Setup

library(data.table)
library(DBI)
library(ggplot2)
library(cowplot)

Attaching package: 'cowplot'
The following object is masked from 'package:ggplot2':

    ggsave
library(grid)

Sys.setlocale("LC_TIME", "en_US.UTF-8") # Print English date format
[1] "en_US.UTF-8"
en_US.UTF-8
# Sys.setlocale("LC_TIME", "nl_NL.UTF-8") # Print Dutch date format

number_format <- scales::number_format(big.mark = ",", decimal.mark = ".") # Print English number format
# number_format <- scales::number_format(big.mark = ".", decimal.mark = ",") # Print Dutch number format

theme_paper <- theme_classic(base_size = 12) + 
  theme(axis.text = element_text(colour = "black"),
        panel.grid.major.y = element_line(colour = "grey92"))

School closure and opening dates

Sources: - https://www.rijksoverheid.nl/actueel/nieuws/2020/03/15/aanvullende-maatregelen-onderwijs-horeca-sport - https://www.rijksoverheid.nl/actueel/nieuws/2020/05/19/onderwijs-gaat-stap-voor-stap-open

date_schools_closed <- as.POSIXct("2020-03-16")
date_schools_opened <- as.POSIXct("2020-06-02")

Handle database connections

db_connect <- function() {
  db <- dbConnect(RSQLite::SQLite(), file.path("..", "data", "noordhoff.sqlite"))
  return(db)
}

db_disconnect <- function(db) {
  dbDisconnect(db)
}

Data

The database contains all SlimStampen data collected via Noordhoff’s platform in three courses: Stepping Stones (English), Grandes Lignes (French), and Neue Kontakte (German).

Trial-level response data are stored in the responses table. Book information, such as the course year, book title, and chapter, are stored in the book_info table.

responses

Column Type Explanation
date int UNIX time stamp [s]
user_id chr unique user identifier
method chr course
start_time int elapsed time since session start [ms]
rt int response time [ms]
duration int trial duration [ms]
fact_id int unique fact identifier (within chapter)
correct int response accuracy
answer chr user’s response
choices int number of answer choices (1 == open response)
backspace_used dbl user pressed backspace during trial
backspace_used_first dbl user erased first character of response
study int trial was a study trial
answer_language chr language of the answer
subsession int identifies part within learning session
book_info_id chr unique identifier of book information

book_info

Column Type Explanation
book_info_id chr unique identifier of book information
method_group chr year and edition
book_title chr book title (incl. year, level, edition)
book_type chr type of book
chapter chr chapter number and title

Preview first 10 rows

db <- db_connect()
responses_top <- dbGetQuery(db, "SELECT * FROM responses LIMIT 10")
responses_top
book_info_top <- dbGetQuery(db, "SELECT * FROM book_info LIMIT 10")
book_info_top
db_disconnect(db)

Usage

Get number of trials by method, day, and user:

db <- db_connect()
counts <- dbGetQuery(db,"SELECT responses.method AS 'method',
                          DATE(responses.date + 3600, 'unixepoch') AS 'doy',
                          responses.user_id AS 'user',
                          COUNT(*) AS 'trials'
                          FROM 'responses'
                          GROUP BY responses.method,
                          DATE(responses.date  + 3600, 'unixepoch'),
                          responses.user_id
                        ")
db_disconnect(db)

setDT(counts)

Add a school year column (cutoff date: 1 August):

counts[, doy_posix := as.POSIXct(doy)]
counts[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Add more sensible course names:

counts[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Total number of unique users by course

counts[, .(unique_users = length(unique(user))), by = .(course, school_year)]

Total number of trials by course

counts[, .(total_trials = sum(trials)), by = .(course, school_year)]

Number of trials by user

counts[, trials_user := sum(trials), by = .(course, user)]
ggplot(counts, aes(x = trials_user)) +
  facet_wrap(~course, ncol = 1, scales = "free_y") +
  geom_histogram(binwidth = 100) +
  labs(x = "Number of trials by user",
       y = NULL) +
  theme_paper

Number of unique days by user

ggplot(counts[, .(N = length(unique(doy_posix))), by = c("user", "course")], aes(x = N)) +
  facet_grid(course ~ ., scales = "free_y") +
  geom_histogram(binwidth = 1) +
  geom_vline(xintercept = (1:7)*7, lty = 2, alpha = 0.5) +
  labs(x = "Number of unique days by user",
       y = NULL) +
  theme_paper

Total number of trials by day

Interpolate missing days:

doy_posix <- seq.POSIXt(from = counts[,min(doy_posix)], to = counts[,max(doy_posix)], by = "DSTday")
course <- counts[,unique(course)]
dates <- CJ(doy_posix, course)
counts <- merge(counts, dates, by = c("doy_posix", "course"), all = TRUE)

Count trials by day:

counts[, trials_total := sum(trials, na.rm = TRUE), by = .(course, doy_posix)]
counts_by_day <- counts[, .(trials_total = sum(trials, na.rm = TRUE)), by = .(course, doy_posix)]
ggplot(counts_by_day[course %in% c("English", "French"),],
       aes(x = doy_posix, y = trials_total, colour = course)) +
  geom_line() +
  scale_x_datetime(date_breaks = "3 months", date_labels = "%e %b %Y") +
  scale_y_continuous(labels = number_format) +
  labs(x = NULL,
       y = "Number of trials per day",
       colour = "Course") +
  theme_paper

Total number of trials by week

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

counts_by_day[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
counts_by_day[, trials_total_week := sum(trials_total, na.rm = TRUE), by = .(course, doy_posix_week)]
ggplot(counts_by_day[course %in% c("English", "French"),],
            aes(x = doy_posix, y = trials_total_week, colour = course)) +
  geom_line() +
  scale_x_datetime(date_breaks = "3 months", date_labels = "%e %b %Y") +
  scale_y_continuous(labels = number_format) +
  labs(x = NULL,
       y = "Number of trials per week",
       colour = "Course") +
  theme_paper

Overlap the two school years:

counts_by_day[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]
counts_by_day[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
counts_by_day[school_year == "19/20", doy_posix_aligned := doy_posix]
p_trial_hist <- ggplot(counts_by_day[course %in% c("English", "French"),],
            aes(x = doy_posix_aligned, ymin = 0, ymax = trials_total_week, group = school_year, colour = school_year, fill = school_year)) +
  facet_wrap(~ course, ncol = 1) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(alpha = .2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 2e6), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

p_trial_hist

ggsave("../output/trial_hist.pdf", width = 5, height = 3)
ggsave("../output/trial_hist.png", width = 5, height = 3)

Make a line-plot version of the histogram.

p_trial_hist_line <- ggplot(counts_by_day[course %in% c("English", "French")],
            aes(x = doy_posix_aligned, y = trials_total_week, group = school_year, colour = school_year, fill = school_year)) +
  facet_wrap(~ course, ncol = 1) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_line() +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 2e6), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

p_trial_hist_line
Warning: Removed 101 row(s) containing missing values (geom_path).

ggsave("../output/trial_hist_line.pdf", width = 5, height = 3)
Warning: Removed 101 row(s) containing missing values (geom_path).
ggsave("../output/trial_hist_line.png", width = 5, height = 3)
Warning: Removed 101 row(s) containing missing values (geom_path).

Also make a difference plot.

# In order for the Mondays to align, move the 18/19 data forward by 1 year - 1 day.
counts_by_day[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 364*24*60*60, origin = "1970-01-01")]
counts_by_day[school_year == "19/20", doy_posix_aligned := doy_posix]
counts_by_day[, year_diff := trials_total_week[2] - trials_total_week[1], by = .(course, doy_posix_aligned)]
ggplot(counts_by_day[course %in% c("English", "French")],
            aes(x = doy_posix_aligned, y = year_diff)) +
  facet_wrap(~ course, ncol = 1) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -1e6, ymax = 1.1e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_hline(yintercept = 0, lty = 3) +
  geom_line() +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(-3e5, 1e6), labels = number_format) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year") +
  theme_paper
Warning: Removed 101 row(s) containing missing values (geom_path).

Number of trials by user and week

counts[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
counts_by_user_and_week <- counts[, .(trials_user = sum(trials, na.rm = TRUE)), by = .(course, school_year, user, doy_posix_week)]

Save for clustering analysis

saveRDS(na.omit(counts_by_user_and_week[course %in% c("English", "French")]), "../data/trials_by_user_and_week.rds")

Unique users by day

users_by_day <- counts[, .(unique_users = length(unique(user))), by = .(course, doy_posix)]
p <- ggplot(users_by_day, aes(x = doy_posix, y = unique_users, colour = course)) +
  geom_line() +
  scale_x_datetime(date_breaks = "3 months", date_labels = "%e %b %Y") +
  scale_y_continuous(labels = number_format) +
  labs(x = NULL,
       y = "Number of users per day",
       colour = "Course") +
  theme_paper

p

Unique users by week

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

users_by_day[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
users_by_week <- counts[, .(unique_users_week = length(unique(user))), by = .(course, doy_posix_week)]
users_by_week <- users_by_day[users_by_week, on = .(course, doy_posix_week)]
p <- ggplot(users_by_week, aes(x = doy_posix, ymin = 0, ymax = unique_users_week, group = course, colour = course, fill = course)) +
  facet_wrap(~ course, ncol = 1, scales = "free_y") +
  geom_ribbon(alpha = .2) +
  scale_x_datetime(date_breaks = "3 months", date_labels = "%e %b %Y") +
  scale_y_continuous(labels = scales::number_format(big.mark = ".", decimal.mark = ",")) +
  labs(x = NULL,
       y = "Aantal gebruikers",
       title = "Aantal verschillende gebruikers per week",
       caption = "Let op: schaal verschilt tussen de grafieken",
       colour = "Lesmethode",
       fill = "Lesmethode") +
  guides(colour = FALSE, fill = FALSE) +
  theme_paper

p

Overlap the two school years:

users_by_week[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]
users_by_week[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
users_by_week[school_year == "19/20", doy_posix_aligned := doy_posix]
p_user_hist <- ggplot(users_by_week[course %in% c("English", "French"),],
            aes(x = doy_posix_aligned, ymin = 0, ymax = unique_users_week, group = school_year, colour = school_year, fill = school_year)) +
  facet_wrap(~ course, ncol = 1) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -800, ymax = 8800, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(alpha = .2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 8000), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Unique users per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

p_user_hist

ggsave("../output/user_hist.pdf", width = 5, height = 3)
ggsave("../output/user_hist.png", width = 5, height = 3)

Make a combined plot of trial and user counts for in the paper:

p_legend <- get_legend(p_trial_hist)

p_trial_hist <- p_trial_hist +
  guides(colour = FALSE, fill = FALSE)

p_user_hist <- p_user_hist +
  guides(colour = FALSE, fill = FALSE)
plot_grid(plot_grid(p_trial_hist, p_user_hist,
          labels = c("A", "B"),
          align = "v", axis = "tblr"),
          p_legend,
          rel_widths = c(1, .2))

ggsave("../output/combi_hist.pdf", width = 9, height = 3)
ggsave("../output/combi_hist.png", width = 9, height = 3)

Activity during the week

Get number of trials by method, day, hour, and user:

db <- db_connect()
counts_by_hour <- dbGetQuery(db,"SELECT responses.method AS 'method',
                          DATE(responses.date + 3600, 'unixepoch') AS 'doy',
                          STRFTIME('%H', responses.date + 3600, 'unixepoch') AS 'hour',
                          responses.user_id AS 'user',
                          COUNT(*) AS 'trials'
                          FROM 'responses'
                          GROUP BY responses.method,
                          DATE(responses.date + 3600, 'unixepoch'),
                          STRFTIME('%H', responses.date + 3600, 'unixepoch'),
                          responses.user_id
                        ")
db_disconnect(db)

setDT(counts_by_hour)

Interpolate missing days and hours:

counts_by_hour[, doy_posix := as.POSIXct(doy)]
counts_by_hour[, hour := as.numeric(hour)]
doy_posix <- seq.POSIXt(from = counts_by_hour[,min(doy_posix)], to = counts_by_hour[,max(doy_posix)], by = "DSTday")
counts_by_hour
method <- counts_by_hour[,unique(method)]
hour <- 0:23
dates_and_hours <- CJ(doy_posix, hour, method)
counts_by_hour <- merge(counts_by_hour, dates_and_hours, by = c("doy_posix", "hour", "method"), all = TRUE)

Add day of the week:

counts_by_hour[, weekday := weekdays(doy_posix)]

Distinguish between school years:

counts_by_hour[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Add quarter:

counts_by_hour[, quarter := paste0(year(doy_posix), "Q", quarter(doy_posix))]

Add exact school closure period in both school years:

counts_by_hour[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
counts_by_hour[school_year == "19/20", doy_posix_aligned := doy_posix]
counts_by_hour[, schools_closed := doy_posix_aligned >= date_schools_closed & doy_posix < date_schools_opened]

Add more sensible course names:

counts_by_hour[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Sum trials by school year, weekday and hour:

counts_by_hour[, trials_schoolyear := sum(trials, na.rm = TRUE), by = .(course, school_year, weekday, hour)]

Also sum trials by quarter, weekday and hour:

counts_by_hour[, trials_quarter := sum(trials, na.rm = TRUE), by = .(course, quarter, weekday, hour)]

And sum trials within the closure period by weekday and hour:

counts_by_hour[schools_closed == TRUE, trials_closed := sum(trials, na.rm = TRUE), by = .(course, school_year, weekday, hour)]
trials_by_wday_hour <- unique(counts_by_hour, by = c("course", "school_year", "quarter", "schools_closed", "weekday", "hour"))

trials_by_wday_hour[, trials_normalised_schoolyear := trials_schoolyear / sum(trials_schoolyear), by = .(course)]
trials_by_wday_hour[, trials_normalised_quarter := trials_quarter / sum(trials_quarter), by = .(course)]
trials_by_wday_hour[, weekday := ordered(weekday, levels = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))]
# trials_by_wday_hour[, weekday := ordered(weekday, levels = c("maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag", "zondag"))]

Plot heatmap for the whole school year:

ggplot(trials_by_wday_hour[course %in% c("English", "French")],
       aes(x = hour, y = reorder(weekday, dplyr::desc(weekday)), fill = trials_normalised_schoolyear)) + 
  facet_grid(school_year ~ course) +
  geom_tile(colour = "white", size = 0.25) +
  labs(x = "Time of day (hour)",
       y = NULL) +
  scale_x_continuous(expand = c(0,0), breaks = seq(0, 24, 3)) +
  scale_y_discrete(expand = c(0,0)) + 
  scale_fill_viridis_c(option = "A", direction = -1) +
  coord_fixed() +
  guides(fill = FALSE) +
  theme_paper

Plot heatmap per quarter:

ggplot(trials_by_wday_hour, aes(x = hour, y = reorder(weekday, dplyr::desc(weekday)), fill = trials_normalised_quarter)) + 
  facet_grid(quarter ~ method) +
  geom_tile(colour = "white", size = 0.25) +
  labs(x = NULL,
       y = NULL,
       title = "Activiteit per uur gedurende de week",
       caption = "Aantal trials per weekdag en uur in elk kwartaal, genormaliseerd per methode.") +
  scale_x_continuous(expand = c(0,0), breaks = seq(0, 24, 3)) +
  scale_y_discrete(expand = c(0,0)) + 
  scale_fill_viridis_c(option = "A", direction = -1) +
  coord_fixed() +
  guides(fill = FALSE) +
  theme_bw(base_size = 16)

Plot heatmap for the period in which schools were closed:

trials_closed <- unique(trials_by_wday_hour[schools_closed == TRUE, .(course, school_year, weekday, hour, trials_closed)])

trials_closed[, trials_normalised_closed := trials_closed / sum(trials_closed), by = .(course, school_year)]
trials_closed_diff <- trials_closed[, .(school_year = "Change",
                                        trials_closed = trials_closed[school_year == "19/20"] - trials_closed[school_year == "18/19"],
                                        trials_normalised_closed = trials_normalised_closed[school_year == "19/20"] - trials_normalised_closed[school_year == "18/19"]), by = .(course, weekday, hour)]
p_heatmap <- ggplot(trials_closed[course %in% c("English", "French"),],
       aes(x = hour, y = reorder(weekday, dplyr::desc(weekday)), fill = trials_normalised_closed)) + 
  facet_grid(school_year ~ course) +
  geom_tile(colour = "white", size = 0.25) +
  labs(x = "Time of day (hour)",
       y = NULL,
       fill = NULL) +
  scale_x_continuous(expand = c(0,0), breaks = seq(0, 24, 3)) +
  scale_y_discrete(expand = c(0,0)) + 
  scale_fill_viridis_c(option = "A", direction = -1) +
  coord_fixed() +
  theme_paper


p_heatmap

Make a plot of the difference between the two school years during the school closure period:

p_heatmap_diff <- ggplot(trials_closed_diff[course %in% c("English", "French"),],
       aes(x = hour, y = reorder(weekday, dplyr::desc(weekday)), fill = trials_normalised_closed)) + 
  facet_grid(school_year ~ course) +
  geom_tile(colour = "white", size = 0.25) +
  labs(x = "Time of day (hour)",
       y = NULL,
       fill = NULL) +
  scale_x_continuous(expand = c(0,0), breaks = seq(0, 24, 3)) +
  scale_y_discrete(expand = c(0,0)) + 
  scale_fill_distiller(type = "div", palette = "RdBu", direction = -1, limits = c(-1, 1) * max(abs(trials_closed_diff[course %in% c("English", "French"),]$trials_normalised_closed))) +
  coord_fixed() +
  theme_paper

p_heatmap_diff

Make a combined plot for in the paper:

p_heatmap_legend <- get_legend(p_heatmap)
p_heatmap_diff_legend <- get_legend(p_heatmap_diff)

p_heatmap <- p_heatmap + guides(fill = FALSE)
p_heatmap_diff <- p_heatmap_diff + guides(fill = FALSE)
plot_grid(
  plot_grid(p_heatmap, p_heatmap_diff,
          ncol = 1,
          labels = c("A", "B"),
          rel_heights = c(1, .655)
          ),
  plot_grid(p_heatmap_legend, p_heatmap_diff_legend,
            ncol = 1,
            align = "vh", axis = "lrtb"),
  ncol = 2,
  rel_widths = c(1, .15))

ggsave("../output/combi_heatmap.pdf", width = 9, height = 5)
ggsave("../output/combi_heatmap.png", width = 9, height = 5)

Activity stratified by year and level

db <- db_connect()
counts_strat <- dbGetQuery(db,"SELECT responses.method AS 'method',
                          responses.book_info_id as 'book_info_id',
                          DATE(responses.date + 3600, 'unixepoch') AS 'doy',
                          responses.user_id AS 'user',
                          COUNT(*) AS 'trials'
                          FROM 'responses'
                          GROUP BY responses.method, responses.book_info_id,
                          DATE(responses.date + 3600, 'unixepoch'),
                          responses.user_id
                        ")
db_disconnect(db)

setDT(counts_strat)
db <- db_connect()
book_info <- dbGetQuery(db, "SELECT * FROM 'book_info'")
db_disconnect(db)

setDT(book_info)

Add book information:

counts_strat[book_info, on = "book_info_id", c("book_title", "method_group") := .(i.book_title, i.method_group)]

Add a school year column (cutoff date: 1 August):

counts_strat[, doy_posix := as.POSIXct(doy)]
counts_strat[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Add sensible course names:

counts_strat[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Count trials by day:

counts_strat_by_day <- counts_strat[, .(trials_total = sum(trials, na.rm = TRUE)), by = .(school_year, course, method_group, book_title, doy_posix)]
setorder(counts_strat_by_day, school_year, course, method_group, book_title, doy_posix)

Simplify level names:

# Keep all distinctions
counts_strat_by_day[, book_title_simple := stringr::str_sub(book_title, 3, -10)]
counts_strat_by_day[, book_title_simple := factor(book_title_simple, levels = c("vmbo b/lwoo", "vmbo b", "vmbo bk", "vmbo k", "vmbo kgt", "vmbo-gt", "vmbo gt", "vmbo-gt/havo", "vmbo (t)hv", "havo", "havo vwo", "vwo"))]
# Simplify to three levels
counts_strat_by_day[, level := dplyr::case_when(
  grepl("vmbo", book_title) ~ "Pre-vocational\n(vmbo)",
  grepl("havo", book_title) ~ "General secondary\n(havo)",
  grepl("vwo", book_title) ~ "Pre-university\n(vwo)",
  TRUE ~ "Other")]
counts_strat_by_day[, level := factor(level, levels = c("Other", "Pre-vocational\n(vmbo)", "General secondary\n(havo)", "Pre-university\n(vwo)"))]

Simplify year names:

counts_strat_by_day[, year := dplyr::case_when(
  method_group == "Leerjaar 1 (5e Ed.)" ~ "Year 1",
  method_group == "Leerjaar 2 (5e Ed.)" ~ "Year 2",
  method_group == "Leerjaar 3 (5e Ed.)" ~ "Year 3",
  method_group == "Leerjaar 3/4 (5e Ed.)" ~ "Year 3/4",
  method_group == "Leerjaar 4 (5e Ed.)" ~ "Year 4",
  method_group == "Tweede Fase (6e Ed.)" ~ "Tweede Fase",
  TRUE ~ "Other")]

Align school years:

counts_strat_by_day[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
counts_strat_by_day[school_year == "19/20", doy_posix_aligned := doy_posix]

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

counts_strat_by_day[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
counts_strat_by_day[, trials_total_week := sum(trials_total, na.rm = TRUE), by = .(school_year, course, method_group, book_title_simple, doy_posix_aligned_week)]
counts_strat_by_day[, trials_total_week_level := sum(trials_total), by = .(school_year, course, method_group, level, doy_posix_aligned_week)]

Summarise increase during lockdown:

counts_strat_increase <- counts_strat_by_day[between(doy_posix_aligned, date_schools_closed, date_schools_opened), .(trials_lockdown = sum(trials_total)), by = .(course, book_title_simple, method_group, year, school_year)]
counts_strat_increase[, increase := trials_lockdown[2]/trials_lockdown[1], by = .(course, book_title_simple, method_group, year)]
counts_strat_increase[, increase_pct := paste0("Change:\n", scales::percent(increase, accuracy = 2))]
counts_strat_increase_level <- counts_strat_by_day[between(doy_posix_aligned, date_schools_closed, date_schools_opened), .(trials_lockdown = sum(trials_total)), by = .(course, level, method_group, year, school_year)]
counts_strat_increase_level[, increase := trials_lockdown[2]/trials_lockdown[1], by = .(course, level, method_group, year)]
counts_strat_increase_level[, increase_pct := paste0("Change:\n", scales::percent(increase, accuracy = 2))]

French

ggplot(counts_strat_by_day[course == "French"], 
       aes(group = school_year, colour = school_year, fill = school_year)) +
  facet_grid(book_title_simple ~ method_group) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(aes(x = doy_posix_aligned, ymin = 0, ymax = trials_total_week, ), alpha = .2) +
  geom_text(data = counts_strat_increase[course == "French" & school_year == "19/20"], 
            aes(label = increase_pct),
            x = as.POSIXct((as.numeric(date_schools_closed) + as.numeric(date_schools_opened))/2, origin = "1970-01-01"),
            y = 3.6e5,
            colour = "black",
            vjust = 1,
            show.legend = FALSE) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 3.75e5), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year",
       title = "French") +
  theme_paper

ggsave("../output/trial_hist_french.pdf", width = 14, height = 10)
ggsave("../output/trial_hist_french.png", width = 14, height = 10)

Streamlined version for in the paper:

ggplot(counts_strat_by_day[course == "French"], 
       aes(group = school_year, colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(aes(x = doy_posix_aligned, ymin = 0, ymax = trials_total_week_level, ), alpha = .2) +
  geom_text(data = counts_strat_increase_level[course == "French" & school_year == "19/20"], 
            aes(label = increase_pct),
            x = as.POSIXct((as.numeric(date_schools_closed) + as.numeric(date_schools_opened))/2, origin = "1970-01-01"),
            y = 3.6e5,
            colour = "black",
            vjust = 1,
            size = rel(2.75),
            show.legend = FALSE) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 3.75e5), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

ggsave("../output/trial_hist_french_level.pdf", width = 9, height = 5)
ggsave("../output/trial_hist_french_level.png", width = 9, height = 5)

English

ggplot(counts_strat_by_day[course == "English"], 
       aes(group = school_year, colour = school_year, fill = school_year)) +
  facet_grid(book_title_simple ~ method_group) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(aes(x = doy_posix_aligned, ymin = 0, ymax = trials_total_week, ), alpha = .2) +
  geom_text(data = counts_strat_increase[course == "English" & school_year == "19/20"], 
            aes(label = increase_pct),
            x = as.POSIXct((as.numeric(date_schools_closed) + as.numeric(date_schools_opened))/2, origin = "1970-01-01"),
            y = 3.6e5,
            colour = "black",
            vjust = 1,
            show.legend = FALSE) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 3.75e5), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year",
       title = "English") +
  theme_paper

ggsave("../output/trial_hist_english.pdf", width = 14, height = 10)
ggsave("../output/trial_hist_english.png", width = 14, height = 10)

Streamlined version for in the paper:

ggplot(counts_strat_by_day[course == "English" & level != "Other"], 
       aes(group = school_year, colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(aes(x = doy_posix_aligned, ymin = 0, ymax = trials_total_week_level, ), alpha = .2) +
  geom_text(data = counts_strat_increase_level[course == "English" & level != "Other" & school_year == "19/20"], 
            aes(label = increase_pct),
            x = as.POSIXct((as.numeric(date_schools_closed) + as.numeric(date_schools_opened))/2, origin = "1970-01-01"),
            y = 9.6e5,
            colour = "black",
            vjust = 1,
            size = rel(2.75),
            show.legend = FALSE) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 1e6), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

ggsave("../output/trial_hist_english_level.pdf", width = 9, height = 5)
ggsave("../output/trial_hist_english_level.png", width = 9, height = 5)

Question type

There are different question formats: open-answer, in which the student types the answer, and multiple-choice, in which the student selects the answer from a set of 3 or 4 options.

db <- db_connect()
question_type <- dbGetQuery(db, 
                      "SELECT r.method AS 'method',
                      DATE(r.date + 3600, 'unixepoch') AS 'doy',
                      r.choices AS 'choices',
                      COUNT(*) AS 'n'
                      FROM 'responses' r
                      WHERE r.study == 0
                      GROUP BY r.method,
                      DATE(r.date + 3600, 'unixepoch'),
                      r.choices"
)
setDT(question_type)
db_disconnect(db)

Add a school year column (cutoff date: 1 August):

question_type[, doy_posix := as.POSIXct(doy)]
question_type[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Add sensible course names:

question_type[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Align school years:

question_type[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
question_type[school_year == "19/20", doy_posix_aligned := doy_posix]

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

question_type[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
question_type[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
question_type_by_week <- question_type[, .(n = sum(n)), by = .(course, school_year, doy_posix_aligned_week, choices)]
ggplot(question_type_by_week[course %in% c("English", "French")], aes(x = as.POSIXct(doy_posix_aligned_week), y = n, group = interaction(school_year,as.factor(choices)), colour = school_year)) +
  facet_grid(course ~ choices) +
  geom_line() +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  labs(x = NULL,
       y = "Trials",
       colour = "School year") +
  theme_paper
Warning: Removed 24 row(s) containing missing values (geom_path).

question_type[, .(n = sum(n)), by = .(course, mcq = choices>1, school_year)][, .(perc_mcq = n[mcq == TRUE]/sum(n)), by = .(course, school_year)]

There is a clear difference between the languages in the question format used: English uses almost exclusively 4-alternative MCQs, while French uses a mix of MCQs (including a small number of 3-alternative questions) and open-answer questions.

Session info

sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=nl_NL.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=nl_NL.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=nl_NL.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
[1] cowplot_0.9.4     ggplot2_3.3.2     DBI_1.1.0         data.table_1.12.2

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.2         RColorBrewer_1.1-2 pillar_1.4.2      
 [4] compiler_3.6.3     base64enc_0.1-3    tools_3.6.3       
 [7] digest_0.6.19      bit_1.1-14         viridisLite_0.3.0 
[10] memoise_1.1.0      jsonlite_1.6       evaluate_0.14     
[13] RSQLite_2.2.0      tibble_2.1.3       gtable_0.3.0      
[16] pkgconfig_2.0.2    rlang_0.4.4        yaml_2.2.0        
[19] xfun_0.7           withr_2.3.0        stringr_1.4.0     
[22] dplyr_0.8.3        knitr_1.23         vctrs_0.2.2       
[25] bit64_0.9-7        tidyselect_0.2.5   glue_1.3.1        
[28] R6_2.4.0           rmarkdown_1.13     blob_1.2.1        
[31] purrr_0.3.2        magrittr_1.5       scales_1.0.0      
[34] htmltools_0.3.6    assertthat_0.2.1   colorspace_1.4-1  
[37] labeling_0.3       stringi_1.4.3      munsell_0.5.0     
[40] crayon_1.3.4      
LS0tCnRpdGxlOiAnU2xpbVN0YW1wZW4gVXNhZ2UgRHVyaW5nIExvY2tkb3duJwphdXRob3I6ICJNYWFydGVuIHZhbiBkZXIgVmVsZGUiCmRhdGU6ICJMYXN0IHVwZGF0ZWQ6IGByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHNtYXJ0OiBubwogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiBubwogICAgdG9jX2Zsb2F0OiB5ZXMKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgojIFNldHVwCgpgYGB7cn0KbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KERCSSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkoZ3JpZCkKClN5cy5zZXRsb2NhbGUoIkxDX1RJTUUiLCAiZW5fVVMuVVRGLTgiKSAjIFByaW50IEVuZ2xpc2ggZGF0ZSBmb3JtYXQKIyBTeXMuc2V0bG9jYWxlKCJMQ19USU1FIiwgIm5sX05MLlVURi04IikgIyBQcmludCBEdXRjaCBkYXRlIGZvcm1hdAoKbnVtYmVyX2Zvcm1hdCA8LSBzY2FsZXM6Om51bWJlcl9mb3JtYXQoYmlnLm1hcmsgPSAiLCIsIGRlY2ltYWwubWFyayA9ICIuIikgIyBQcmludCBFbmdsaXNoIG51bWJlciBmb3JtYXQKIyBudW1iZXJfZm9ybWF0IDwtIHNjYWxlczo6bnVtYmVyX2Zvcm1hdChiaWcubWFyayA9ICIuIiwgZGVjaW1hbC5tYXJrID0gIiwiKSAjIFByaW50IER1dGNoIG51bWJlciBmb3JtYXQKCnRoZW1lX3BhcGVyIDwtIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTIpICsgCiAgdGhlbWUoYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJibGFjayIpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiZ3JleTkyIikpCmBgYAoKU2Nob29sIGNsb3N1cmUgYW5kIG9wZW5pbmcgZGF0ZXMKClNvdXJjZXM6Ci0gaHR0cHM6Ly93d3cucmlqa3NvdmVyaGVpZC5ubC9hY3R1ZWVsL25pZXV3cy8yMDIwLzAzLzE1L2FhbnZ1bGxlbmRlLW1hYXRyZWdlbGVuLW9uZGVyd2lqcy1ob3JlY2Etc3BvcnQKLSBodHRwczovL3d3dy5yaWprc292ZXJoZWlkLm5sL2FjdHVlZWwvbmlldXdzLzIwMjAvMDUvMTkvb25kZXJ3aWpzLWdhYXQtc3RhcC12b29yLXN0YXAtb3BlbgpgYGB7cn0KZGF0ZV9zY2hvb2xzX2Nsb3NlZCA8LSBhcy5QT1NJWGN0KCIyMDIwLTAzLTE2IikKZGF0ZV9zY2hvb2xzX29wZW5lZCA8LSBhcy5QT1NJWGN0KCIyMDIwLTA2LTAyIikKYGBgCgoKSGFuZGxlIGRhdGFiYXNlIGNvbm5lY3Rpb25zCmBgYHtyfQpkYl9jb25uZWN0IDwtIGZ1bmN0aW9uKCkgewogIGRiIDwtIGRiQ29ubmVjdChSU1FMaXRlOjpTUUxpdGUoKSwgZmlsZS5wYXRoKCIuLiIsICJkYXRhIiwgIm5vb3JkaG9mZi5zcWxpdGUiKSkKICByZXR1cm4oZGIpCn0KCmRiX2Rpc2Nvbm5lY3QgPC0gZnVuY3Rpb24oZGIpIHsKICBkYkRpc2Nvbm5lY3QoZGIpCn0KYGBgCgoKIyBEYXRhCgpUaGUgZGF0YWJhc2UgY29udGFpbnMgYWxsIFNsaW1TdGFtcGVuIGRhdGEgY29sbGVjdGVkIHZpYSBOb29yZGhvZmYncyBwbGF0Zm9ybSBpbiB0aHJlZSBjb3Vyc2VzOiAqU3RlcHBpbmcgU3RvbmVzKiAoRW5nbGlzaCksICpHcmFuZGVzIExpZ25lcyogKEZyZW5jaCksIGFuZCAqTmV1ZSBLb250YWt0ZSogKEdlcm1hbikuCgpUcmlhbC1sZXZlbCByZXNwb25zZSBkYXRhIGFyZSBzdG9yZWQgaW4gdGhlIGByZXNwb25zZXNgIHRhYmxlLgpCb29rIGluZm9ybWF0aW9uLCBzdWNoIGFzIHRoZSBjb3Vyc2UgeWVhciwgYm9vayB0aXRsZSwgYW5kIGNoYXB0ZXIsIGFyZSBzdG9yZWQgaW4gdGhlIGBib29rX2luZm9gIHRhYmxlLgoKIyMgYHJlc3BvbnNlc2AKCnwgQ29sdW1uICAgICAgICAgICAgICAgfCBUeXBlICAgICAgfCBFeHBsYW5hdGlvbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwKfCBkYXRlICAgICAgICAgICAgICAgICB8IGludCAgICAgICB8IFVOSVggdGltZSBzdGFtcCBbc10gICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgdXNlcl9pZCAgICAgICAgICAgICAgfCBjaHIgICAgICAgfCB1bmlxdWUgdXNlciBpZGVudGlmaWVyICAgICAgICAgICAgICAgICAgICAgICAgfAp8IG1ldGhvZCAgICAgICAgICAgICAgIHwgY2hyICAgICAgIHwgY291cnNlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBzdGFydF90aW1lICAgICAgICAgICB8IGludCAgICAgICB8IGVsYXBzZWQgdGltZSBzaW5jZSBzZXNzaW9uIHN0YXJ0IFttc10gICAgICAgICB8CnwgcnQgICAgICAgICAgICAgICAgICAgfCBpbnQgICAgICAgfCByZXNwb25zZSB0aW1lIFttc10gICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGR1cmF0aW9uICAgICAgICAgICAgIHwgaW50ICAgICAgIHwgdHJpYWwgZHVyYXRpb24gW21zXSAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBmYWN0X2lkICAgICAgICAgICAgICB8IGludCAgICAgICB8IHVuaXF1ZSBmYWN0IGlkZW50aWZpZXIgKHdpdGhpbiBjaGFwdGVyKSAgICAgICB8CnwgY29ycmVjdCAgICAgICAgICAgICAgfCBpbnQgICAgICAgfCByZXNwb25zZSBhY2N1cmFjeSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGFuc3dlciAgICAgICAgICAgICAgIHwgY2hyICAgICAgIHwgdXNlcidzIHJlc3BvbnNlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBjaG9pY2VzICAgICAgICAgICAgICB8IGludCAgICAgICB8IG51bWJlciBvZiBhbnN3ZXIgY2hvaWNlcyAoMSA9PSBvcGVuIHJlc3BvbnNlKSB8CnwgYmFja3NwYWNlX3VzZWQgICAgICAgfCBkYmwgICAgICAgfCB1c2VyIHByZXNzZWQgYmFja3NwYWNlIGR1cmluZyB0cmlhbCAgICAgICAgICAgfAp8IGJhY2tzcGFjZV91c2VkX2ZpcnN0IHwgZGJsICAgICAgIHwgdXNlciBlcmFzZWQgZmlyc3QgY2hhcmFjdGVyIG9mIHJlc3BvbnNlICAgICAgIHwKfCBzdHVkeSAgICAgICAgICAgICAgICB8IGludCAgICAgICB8IHRyaWFsIHdhcyBhIHN0dWR5IHRyaWFsICAgICAgICAgICAgICAgICAgICAgICB8CnwgYW5zd2VyX2xhbmd1YWdlICAgICAgfCBjaHIgICAgICAgfCBsYW5ndWFnZSBvZiB0aGUgYW5zd2VyICAgICAgICAgICAgICAgICAgICAgICAgfAp8IHN1YnNlc3Npb24gICAgICAgICAgIHwgaW50ICAgICAgIHwgaWRlbnRpZmllcyBwYXJ0IHdpdGhpbiBsZWFybmluZyBzZXNzaW9uICAgICAgIHwKfCBib29rX2luZm9faWQgICAgICAgICB8IGNociAgICAgICB8IHVuaXF1ZSBpZGVudGlmaWVyIG9mIGJvb2sgaW5mb3JtYXRpb24gICAgICAgICB8CgoKIyMgYGJvb2tfaW5mb2AKCnwgQ29sdW1uICAgICAgICAgICAgICAgfCBUeXBlICAgICAgfCBFeHBsYW5hdGlvbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwKfCBib29rX2luZm9faWQgICAgICAgICB8IGNociAgICAgICB8IHVuaXF1ZSBpZGVudGlmaWVyIG9mIGJvb2sgaW5mb3JtYXRpb24gICAgICAgICB8CnwgbWV0aG9kX2dyb3VwICAgICAgICAgfCBjaHIgICAgICAgfCB5ZWFyIGFuZCBlZGl0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGJvb2tfdGl0bGUgICAgICAgICAgIHwgY2hyICAgICAgIHwgYm9vayB0aXRsZSAoaW5jbC4geWVhciwgbGV2ZWwsIGVkaXRpb24pICAgICAgIHwKfCBib29rX3R5cGUgICAgICAgICAgICB8IGNociAgICAgICB8IHR5cGUgb2YgYm9vayAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgY2hhcHRlciAgICAgICAgICAgICAgfCBjaHIgICAgICAgfCBjaGFwdGVyIG51bWJlciBhbmQgdGl0bGUgICAgICAgICAgICAgICAgICAgICAgfAoKClByZXZpZXcgZmlyc3QgMTAgcm93cwpgYGB7cn0KZGIgPC0gZGJfY29ubmVjdCgpCnJlc3BvbnNlc190b3AgPC0gZGJHZXRRdWVyeShkYiwgIlNFTEVDVCAqIEZST00gcmVzcG9uc2VzIExJTUlUIDEwIikKcmVzcG9uc2VzX3RvcAoKYm9va19pbmZvX3RvcCA8LSBkYkdldFF1ZXJ5KGRiLCAiU0VMRUNUICogRlJPTSBib29rX2luZm8gTElNSVQgMTAiKQpib29rX2luZm9fdG9wCmRiX2Rpc2Nvbm5lY3QoZGIpCmBgYAoKCgojIFVzYWdlCgpHZXQgbnVtYmVyIG9mIHRyaWFscyBieSBtZXRob2QsIGRheSwgYW5kIHVzZXI6CmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKY291bnRzIDwtIGRiR2V0UXVlcnkoZGIsIlNFTEVDVCByZXNwb25zZXMubWV0aG9kIEFTICdtZXRob2QnLAogICAgICAgICAgICAgICAgICAgICAgICAgIERBVEUocmVzcG9uc2VzLmRhdGUgKyAzNjAwLCAndW5peGVwb2NoJykgQVMgJ2RveScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzcG9uc2VzLnVzZXJfaWQgQVMgJ3VzZXInLAogICAgICAgICAgICAgICAgICAgICAgICAgIENPVU5UKCopIEFTICd0cmlhbHMnCiAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSAncmVzcG9uc2VzJwogICAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIHJlc3BvbnNlcy5tZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgREFURShyZXNwb25zZXMuZGF0ZSAgKyAzNjAwLCAndW5peGVwb2NoJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzcG9uc2VzLnVzZXJfaWQKICAgICAgICAgICAgICAgICAgICAgICAgIikKZGJfZGlzY29ubmVjdChkYikKCnNldERUKGNvdW50cykKYGBgCgpBZGQgYSBzY2hvb2wgeWVhciBjb2x1bW4gKGN1dG9mZiBkYXRlOiAxIEF1Z3VzdCk6CmBgYHtyfQpjb3VudHNbLCBkb3lfcG9zaXggOj0gYXMuUE9TSVhjdChkb3kpXQpjb3VudHNbLCBzY2hvb2xfeWVhciA6PSBpZmVsc2UoZG95X3Bvc2l4IDwgIjIwMTktMDgtMDEiLCAiMTgvMTkiLCAiMTkvMjAiKV0KYGBgCgpBZGQgbW9yZSBzZW5zaWJsZSBjb3Vyc2UgbmFtZXM6CmBgYHtyfQpjb3VudHNbLCBjb3Vyc2UgOj0gaWZlbHNlKG1ldGhvZCA9PSAiR3JhbmRlcyBMaWduZXMiLCAiRnJlbmNoIiwgaWZlbHNlKG1ldGhvZCA9PSAiU3RlcHBpbmcgU3RvbmVzIiwgIkVuZ2xpc2giLCAiR2VybWFuIikpXQpgYGAKCgojIyBUb3RhbCBudW1iZXIgb2YgdW5pcXVlIHVzZXJzIGJ5IGNvdXJzZQpgYGB7cn0KY291bnRzWywgLih1bmlxdWVfdXNlcnMgPSBsZW5ndGgodW5pcXVlKHVzZXIpKSksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyKV0KYGBgCgoKIyMgVG90YWwgbnVtYmVyIG9mIHRyaWFscyBieSBjb3Vyc2UKYGBge3J9CmNvdW50c1ssIC4odG90YWxfdHJpYWxzID0gc3VtKHRyaWFscykpLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhcildCmBgYAoKIyMgTnVtYmVyIG9mIHRyaWFscyBieSB1c2VyCgpgYGB7cn0KY291bnRzWywgdHJpYWxzX3VzZXIgOj0gc3VtKHRyaWFscyksIGJ5ID0gLihjb3Vyc2UsIHVzZXIpXQpnZ3Bsb3QoY291bnRzLCBhZXMoeCA9IHRyaWFsc191c2VyKSkgKwogIGZhY2V0X3dyYXAofmNvdXJzZSwgbmNvbCA9IDEsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxMDApICsKICBsYWJzKHggPSAiTnVtYmVyIG9mIHRyaWFscyBieSB1c2VyIiwKICAgICAgIHkgPSBOVUxMKSArCiAgdGhlbWVfcGFwZXIKCmBgYAoKIyMgTnVtYmVyIG9mIHVuaXF1ZSBkYXlzIGJ5IHVzZXIKYGBge3J9CmdncGxvdChjb3VudHNbLCAuKE4gPSBsZW5ndGgodW5pcXVlKGRveV9wb3NpeCkpKSwgYnkgPSBjKCJ1c2VyIiwgImNvdXJzZSIpXSwgYWVzKHggPSBOKSkgKwogIGZhY2V0X2dyaWQoY291cnNlIH4gLiwgc2NhbGVzID0gImZyZWVfeSIpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAoMTo3KSo3LCBsdHkgPSAyLCBhbHBoYSA9IDAuNSkgKwogIGxhYnMoeCA9ICJOdW1iZXIgb2YgdW5pcXVlIGRheXMgYnkgdXNlciIsCiAgICAgICB5ID0gTlVMTCkgKwogIHRoZW1lX3BhcGVyCmBgYAoKCiMjIFRvdGFsIG51bWJlciBvZiB0cmlhbHMgYnkgZGF5CgpJbnRlcnBvbGF0ZSBtaXNzaW5nIGRheXM6CmBgYHtyfQpkb3lfcG9zaXggPC0gc2VxLlBPU0lYdChmcm9tID0gY291bnRzWyxtaW4oZG95X3Bvc2l4KV0sIHRvID0gY291bnRzWyxtYXgoZG95X3Bvc2l4KV0sIGJ5ID0gIkRTVGRheSIpCmNvdXJzZSA8LSBjb3VudHNbLHVuaXF1ZShjb3Vyc2UpXQpkYXRlcyA8LSBDSihkb3lfcG9zaXgsIGNvdXJzZSkKY291bnRzIDwtIG1lcmdlKGNvdW50cywgZGF0ZXMsIGJ5ID0gYygiZG95X3Bvc2l4IiwgImNvdXJzZSIpLCBhbGwgPSBUUlVFKQpgYGAKCkNvdW50IHRyaWFscyBieSBkYXk6CmBgYHtyfQpjb3VudHNbLCB0cmlhbHNfdG90YWwgOj0gc3VtKHRyaWFscywgbmEucm0gPSBUUlVFKSwgYnkgPSAuKGNvdXJzZSwgZG95X3Bvc2l4KV0KYGBgCgpgYGB7cn0KY291bnRzX2J5X2RheSA8LSBjb3VudHNbLCAuKHRyaWFsc190b3RhbCA9IHN1bSh0cmlhbHMsIG5hLnJtID0gVFJVRSkpLCBieSA9IC4oY291cnNlLCBkb3lfcG9zaXgpXQpgYGAKCgpgYGB7cn0KZ2dwbG90KGNvdW50c19ieV9kYXlbY291cnNlICVpbiUgYygiRW5nbGlzaCIsICJGcmVuY2giKSxdLAogICAgICAgYWVzKHggPSBkb3lfcG9zaXgsIHkgPSB0cmlhbHNfdG90YWwsIGNvbG91ciA9IGNvdXJzZSkpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeF9kYXRldGltZShkYXRlX2JyZWFrcyA9ICIzIG1vbnRocyIsIGRhdGVfbGFiZWxzID0gIiVlICViICVZIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBudW1iZXJfZm9ybWF0KSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiTnVtYmVyIG9mIHRyaWFscyBwZXIgZGF5IiwKICAgICAgIGNvbG91ciA9ICJDb3Vyc2UiKSArCiAgdGhlbWVfcGFwZXIKYGBgCgoKIyMgVG90YWwgbnVtYmVyIG9mIHRyaWFscyBieSB3ZWVrCgpVc2UgY3V0LkRhdGUoKSB0byBiaW4gZGF0ZXMgYnkgd2Vlay4gRWFjaCBkYXkgaXMgYXNzaWduZWQgdGhlIGRhdGUgb2YgdGhlIG1vc3QgcmVjZW50IE1vbmRheS4KYGBge3J9CmNvdW50c19ieV9kYXlbLCBkb3lfcG9zaXhfd2VlayA6PSBjdXQuUE9TSVh0KGRveV9wb3NpeCwgIndlZWsiKV0KY291bnRzX2J5X2RheVssIHRyaWFsc190b3RhbF93ZWVrIDo9IHN1bSh0cmlhbHNfdG90YWwsIG5hLnJtID0gVFJVRSksIGJ5ID0gLihjb3Vyc2UsIGRveV9wb3NpeF93ZWVrKV0KYGBgCgoKYGBge3J9CmdncGxvdChjb3VudHNfYnlfZGF5W2NvdXJzZSAlaW4lIGMoIkVuZ2xpc2giLCAiRnJlbmNoIiksXSwKICAgICAgICAgICAgYWVzKHggPSBkb3lfcG9zaXgsIHkgPSB0cmlhbHNfdG90YWxfd2VlaywgY29sb3VyID0gY291cnNlKSkgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV94X2RhdGV0aW1lKGRhdGVfYnJlYWtzID0gIjMgbW9udGhzIiwgZGF0ZV9sYWJlbHMgPSAiJWUgJWIgJVkiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IG51bWJlcl9mb3JtYXQpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJOdW1iZXIgb2YgdHJpYWxzIHBlciB3ZWVrIiwKICAgICAgIGNvbG91ciA9ICJDb3Vyc2UiKSArCiAgdGhlbWVfcGFwZXIKYGBgCgoKT3ZlcmxhcCB0aGUgdHdvIHNjaG9vbCB5ZWFyczoKYGBge3J9CmNvdW50c19ieV9kYXlbLCBzY2hvb2xfeWVhciA6PSBpZmVsc2UoZG95X3Bvc2l4IDwgIjIwMTktMDgtMDEiLCAiMTgvMTkiLCAiMTkvMjAiKV0KY291bnRzX2J5X2RheVtzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeCArIDM2NSoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KY291bnRzX2J5X2RheVtzY2hvb2xfeWVhciA9PSAiMTkvMjAiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBkb3lfcG9zaXhdCmBgYAoKYGBge3J9CnBfdHJpYWxfaGlzdCA8LSBnZ3Bsb3QoY291bnRzX2J5X2RheVtjb3Vyc2UgJWluJSBjKCJFbmdsaXNoIiwgIkZyZW5jaCIpLF0sCiAgICAgICAgICAgIGFlcyh4ID0gZG95X3Bvc2l4X2FsaWduZWQsIHltaW4gPSAwLCB5bWF4ID0gdHJpYWxzX3RvdGFsX3dlZWssIGdyb3VwID0gc2Nob29sX3llYXIsIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfd3JhcCh+IGNvdXJzZSwgbmNvbCA9IDEpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTJlNSwgeW1heCA9IDIuMmU2LCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyKSArCiAgZ2VvbV9yaWJib24oYWxwaGEgPSAuMikgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTExLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAxLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAzLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA1LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCAyZTYpLCBsYWJlbHMgPSBudW1iZXJfZm9ybWF0KSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlRyaWFscyBwZXIgd2VlayIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIpICsKICB0aGVtZV9wYXBlcgoKcF90cmlhbF9oaXN0CgpnZ3NhdmUoIi4uL291dHB1dC90cmlhbF9oaXN0LnBkZiIsIHdpZHRoID0gNSwgaGVpZ2h0ID0gMykKZ2dzYXZlKCIuLi9vdXRwdXQvdHJpYWxfaGlzdC5wbmciLCB3aWR0aCA9IDUsIGhlaWdodCA9IDMpCmBgYAoKTWFrZSBhIGxpbmUtcGxvdCB2ZXJzaW9uIG9mIHRoZSBoaXN0b2dyYW0uCmBgYHtyfQpwX3RyaWFsX2hpc3RfbGluZSA8LSBnZ3Bsb3QoY291bnRzX2J5X2RheVtjb3Vyc2UgJWluJSBjKCJFbmdsaXNoIiwgIkZyZW5jaCIpXSwKICAgICAgICAgICAgYWVzKHggPSBkb3lfcG9zaXhfYWxpZ25lZCwgeSA9IHRyaWFsc190b3RhbF93ZWVrLCBncm91cCA9IHNjaG9vbF95ZWFyLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X3dyYXAofiBjb3Vyc2UsIG5jb2wgPSAxKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IC0yZTUsIHltYXggPSAyLjJlNiwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMikgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMy0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgMmU2KSwgbGFiZWxzID0gbnVtYmVyX2Zvcm1hdCkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJUcmlhbHMgcGVyIHdlZWsiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiKSArCiAgdGhlbWVfcGFwZXIKCnBfdHJpYWxfaGlzdF9saW5lCgpnZ3NhdmUoIi4uL291dHB1dC90cmlhbF9oaXN0X2xpbmUucGRmIiwgd2lkdGggPSA1LCBoZWlnaHQgPSAzKQpnZ3NhdmUoIi4uL291dHB1dC90cmlhbF9oaXN0X2xpbmUucG5nIiwgd2lkdGggPSA1LCBoZWlnaHQgPSAzKQpgYGAKCkFsc28gbWFrZSBhIGRpZmZlcmVuY2UgcGxvdC4KYGBge3J9CiMgSW4gb3JkZXIgZm9yIHRoZSBNb25kYXlzIHRvIGFsaWduLCBtb3ZlIHRoZSAxOC8xOSBkYXRhIGZvcndhcmQgYnkgMSB5ZWFyIC0gMSBkYXkuCmNvdW50c19ieV9kYXlbc2Nob29sX3llYXIgPT0gIjE4LzE5IiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gYXMuUE9TSVhjdChkb3lfcG9zaXggKyAzNjQqMjQqNjAqNjAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIildCmNvdW50c19ieV9kYXlbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gZG95X3Bvc2l4XQpgYGAKCmBgYHtyfQpjb3VudHNfYnlfZGF5WywgeWVhcl9kaWZmIDo9IHRyaWFsc190b3RhbF93ZWVrWzJdIC0gdHJpYWxzX3RvdGFsX3dlZWtbMV0sIGJ5ID0gLihjb3Vyc2UsIGRveV9wb3NpeF9hbGlnbmVkKV0KCmdncGxvdChjb3VudHNfYnlfZGF5W2NvdXJzZSAlaW4lIGMoIkVuZ2xpc2giLCAiRnJlbmNoIildLAogICAgICAgICAgICBhZXMoeCA9IGRveV9wb3NpeF9hbGlnbmVkLCB5ID0geWVhcl9kaWZmKSkgKwogIGZhY2V0X3dyYXAofiBjb3Vyc2UsIG5jb2wgPSAxKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IC0xZTYsIHltYXggPSAxLjFlNiwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMikgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGx0eSA9IDMpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLCAKICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGFzLlBPU0lYY3QoYygKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTEtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDEtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDMtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDUtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgbGltaXRzID0gYXMuUE9TSVhjdChjKCIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGRhdGVfbGFiZWxzID0gIiViIikgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsaW1pdHMgPSBjKC0zZTUsIDFlNiksIGxhYmVscyA9IG51bWJlcl9mb3JtYXQpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJUcmlhbHMgcGVyIHdlZWsiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiKSArCiAgdGhlbWVfcGFwZXIKYGBgCgoKIyMgTnVtYmVyIG9mIHRyaWFscyBieSB1c2VyIGFuZCB3ZWVrCmBgYHtyfQpjb3VudHNbLCBkb3lfcG9zaXhfd2VlayA6PSBjdXQuUE9TSVh0KGRveV9wb3NpeCwgIndlZWsiKV0KY291bnRzX2J5X3VzZXJfYW5kX3dlZWsgPC0gY291bnRzWywgLih0cmlhbHNfdXNlciA9IHN1bSh0cmlhbHMsIG5hLnJtID0gVFJVRSkpLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhciwgdXNlciwgZG95X3Bvc2l4X3dlZWspXQpgYGAKClNhdmUgZm9yIGNsdXN0ZXJpbmcgYW5hbHlzaXMKYGBge3J9CnNhdmVSRFMobmEub21pdChjb3VudHNfYnlfdXNlcl9hbmRfd2Vla1tjb3Vyc2UgJWluJSBjKCJFbmdsaXNoIiwgIkZyZW5jaCIpXSksICIuLi9kYXRhL3RyaWFsc19ieV91c2VyX2FuZF93ZWVrLnJkcyIpCmBgYAoKCiMjIFVuaXF1ZSB1c2VycyBieSBkYXkKCmBgYHtyfQp1c2Vyc19ieV9kYXkgPC0gY291bnRzWywgLih1bmlxdWVfdXNlcnMgPSBsZW5ndGgodW5pcXVlKHVzZXIpKSksIGJ5ID0gLihjb3Vyc2UsIGRveV9wb3NpeCldCmBgYAoKYGBge3J9CnAgPC0gZ2dwbG90KHVzZXJzX2J5X2RheSwgYWVzKHggPSBkb3lfcG9zaXgsIHkgPSB1bmlxdWVfdXNlcnMsIGNvbG91ciA9IGNvdXJzZSkpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeF9kYXRldGltZShkYXRlX2JyZWFrcyA9ICIzIG1vbnRocyIsIGRhdGVfbGFiZWxzID0gIiVlICViICVZIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBudW1iZXJfZm9ybWF0KSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiTnVtYmVyIG9mIHVzZXJzIHBlciBkYXkiLAogICAgICAgY29sb3VyID0gIkNvdXJzZSIpICsKICB0aGVtZV9wYXBlcgoKcApgYGAKCgojIyBVbmlxdWUgdXNlcnMgYnkgd2VlawoKVXNlIGN1dC5EYXRlKCkgdG8gYmluIGRhdGVzIGJ5IHdlZWsuIEVhY2ggZGF5IGlzIGFzc2lnbmVkIHRoZSBkYXRlIG9mIHRoZSBtb3N0IHJlY2VudCBNb25kYXkuCmBgYHtyfQp1c2Vyc19ieV9kYXlbLCBkb3lfcG9zaXhfd2VlayA6PSBjdXQuUE9TSVh0KGRveV9wb3NpeCwgIndlZWsiKV0KYGBgCgpgYGB7cn0KdXNlcnNfYnlfd2VlayA8LSBjb3VudHNbLCAuKHVuaXF1ZV91c2Vyc193ZWVrID0gbGVuZ3RoKHVuaXF1ZSh1c2VyKSkpLCBieSA9IC4oY291cnNlLCBkb3lfcG9zaXhfd2VlayldCnVzZXJzX2J5X3dlZWsgPC0gdXNlcnNfYnlfZGF5W3VzZXJzX2J5X3dlZWssIG9uID0gLihjb3Vyc2UsIGRveV9wb3NpeF93ZWVrKV0KYGBgCgpgYGB7cn0KcCA8LSBnZ3Bsb3QodXNlcnNfYnlfd2VlaywgYWVzKHggPSBkb3lfcG9zaXgsIHltaW4gPSAwLCB5bWF4ID0gdW5pcXVlX3VzZXJzX3dlZWssIGdyb3VwID0gY291cnNlLCBjb2xvdXIgPSBjb3Vyc2UsIGZpbGwgPSBjb3Vyc2UpKSArCiAgZmFjZXRfd3JhcCh+IGNvdXJzZSwgbmNvbCA9IDEsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgZ2VvbV9yaWJib24oYWxwaGEgPSAuMikgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZGF0ZV9icmVha3MgPSAiMyBtb250aHMiLCBkYXRlX2xhYmVscyA9ICIlZSAlYiAlWSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpudW1iZXJfZm9ybWF0KGJpZy5tYXJrID0gIi4iLCBkZWNpbWFsLm1hcmsgPSAiLCIpKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiQWFudGFsIGdlYnJ1aWtlcnMiLAogICAgICAgdGl0bGUgPSAiQWFudGFsIHZlcnNjaGlsbGVuZGUgZ2VicnVpa2VycyBwZXIgd2VlayIsCiAgICAgICBjYXB0aW9uID0gIkxldCBvcDogc2NoYWFsIHZlcnNjaGlsdCB0dXNzZW4gZGUgZ3JhZmlla2VuIiwKICAgICAgIGNvbG91ciA9ICJMZXNtZXRob2RlIiwKICAgICAgIGZpbGwgPSAiTGVzbWV0aG9kZSIpICsKICBndWlkZXMoY29sb3VyID0gRkFMU0UsIGZpbGwgPSBGQUxTRSkgKwogIHRoZW1lX3BhcGVyCgpwCmBgYAoKT3ZlcmxhcCB0aGUgdHdvIHNjaG9vbCB5ZWFyczoKYGBge3J9CnVzZXJzX2J5X3dlZWtbLCBzY2hvb2xfeWVhciA6PSBpZmVsc2UoZG95X3Bvc2l4IDwgIjIwMTktMDgtMDEiLCAiMTgvMTkiLCAiMTkvMjAiKV0KdXNlcnNfYnlfd2Vla1tzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeCArIDM2NSoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KdXNlcnNfYnlfd2Vla1tzY2hvb2xfeWVhciA9PSAiMTkvMjAiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBkb3lfcG9zaXhdCmBgYAoKCmBgYHtyfQpwX3VzZXJfaGlzdCA8LSBnZ3Bsb3QodXNlcnNfYnlfd2Vla1tjb3Vyc2UgJWluJSBjKCJFbmdsaXNoIiwgIkZyZW5jaCIpLF0sCiAgICAgICAgICAgIGFlcyh4ID0gZG95X3Bvc2l4X2FsaWduZWQsIHltaW4gPSAwLCB5bWF4ID0gdW5pcXVlX3VzZXJzX3dlZWssIGdyb3VwID0gc2Nob29sX3llYXIsIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfd3JhcCh+IGNvdXJzZSwgbmNvbCA9IDEpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTgwMCwgeW1heCA9IDg4MDAsIGZpbGwgPSAiZ3JleTkyIiwgY29sb3VyID0gImdyZXk1MCIsIGx0eSA9IDIpICsKICBnZW9tX3JpYmJvbihhbHBoYSA9IC4yKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLCAKICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGFzLlBPU0lYY3QoYygKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTEtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDEtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDMtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDUtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgbGltaXRzID0gYXMuUE9TSVhjdChjKCIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGRhdGVfbGFiZWxzID0gIiViIikgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsaW1pdHMgPSBjKDAsIDgwMDApLCBsYWJlbHMgPSBudW1iZXJfZm9ybWF0KSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlVuaXF1ZSB1c2VycyBwZXIgd2VlayIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIpICsKICB0aGVtZV9wYXBlcgoKcF91c2VyX2hpc3QKCmdnc2F2ZSgiLi4vb3V0cHV0L3VzZXJfaGlzdC5wZGYiLCB3aWR0aCA9IDUsIGhlaWdodCA9IDMpCmdnc2F2ZSgiLi4vb3V0cHV0L3VzZXJfaGlzdC5wbmciLCB3aWR0aCA9IDUsIGhlaWdodCA9IDMpCmBgYAoKCk1ha2UgYSBjb21iaW5lZCBwbG90IG9mIHRyaWFsIGFuZCB1c2VyIGNvdW50cyBmb3IgaW4gdGhlIHBhcGVyOgpgYGB7cn0KcF9sZWdlbmQgPC0gZ2V0X2xlZ2VuZChwX3RyaWFsX2hpc3QpCgpwX3RyaWFsX2hpc3QgPC0gcF90cmlhbF9oaXN0ICsKICBndWlkZXMoY29sb3VyID0gRkFMU0UsIGZpbGwgPSBGQUxTRSkKCnBfdXNlcl9oaXN0IDwtIHBfdXNlcl9oaXN0ICsKICBndWlkZXMoY29sb3VyID0gRkFMU0UsIGZpbGwgPSBGQUxTRSkKYGBgCgpgYGB7cn0KcGxvdF9ncmlkKHBsb3RfZ3JpZChwX3RyaWFsX2hpc3QsIHBfdXNlcl9oaXN0LAogICAgICAgICAgbGFiZWxzID0gYygiQSIsICJCIiksCiAgICAgICAgICBhbGlnbiA9ICJ2IiwgYXhpcyA9ICJ0YmxyIiksCiAgICAgICAgICBwX2xlZ2VuZCwKICAgICAgICAgIHJlbF93aWR0aHMgPSBjKDEsIC4yKSkKCmdnc2F2ZSgiLi4vb3V0cHV0L2NvbWJpX2hpc3QucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSAzKQpnZ3NhdmUoIi4uL291dHB1dC9jb21iaV9oaXN0LnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMykKYGBgCgoKIyMgQWN0aXZpdHkgZHVyaW5nIHRoZSB3ZWVrCgpHZXQgbnVtYmVyIG9mIHRyaWFscyBieSBtZXRob2QsIGRheSwgaG91ciwgYW5kIHVzZXI6CmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKY291bnRzX2J5X2hvdXIgPC0gZGJHZXRRdWVyeShkYiwiU0VMRUNUIHJlc3BvbnNlcy5tZXRob2QgQVMgJ21ldGhvZCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgREFURShyZXNwb25zZXMuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSBBUyAnZG95JywKICAgICAgICAgICAgICAgICAgICAgICAgICBTVFJGVElNRSgnJUgnLCByZXNwb25zZXMuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSBBUyAnaG91cicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzcG9uc2VzLnVzZXJfaWQgQVMgJ3VzZXInLAogICAgICAgICAgICAgICAgICAgICAgICAgIENPVU5UKCopIEFTICd0cmlhbHMnCiAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSAncmVzcG9uc2VzJwogICAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIHJlc3BvbnNlcy5tZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgREFURShyZXNwb25zZXMuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBTVFJGVElNRSgnJUgnLCByZXNwb25zZXMuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICByZXNwb25zZXMudXNlcl9pZAogICAgICAgICAgICAgICAgICAgICAgICAiKQpkYl9kaXNjb25uZWN0KGRiKQoKc2V0RFQoY291bnRzX2J5X2hvdXIpCmBgYAoKSW50ZXJwb2xhdGUgbWlzc2luZyBkYXlzIGFuZCBob3VyczoKYGBge3J9CmNvdW50c19ieV9ob3VyWywgZG95X3Bvc2l4IDo9IGFzLlBPU0lYY3QoZG95KV0KY291bnRzX2J5X2hvdXJbLCBob3VyIDo9IGFzLm51bWVyaWMoaG91cildCmRveV9wb3NpeCA8LSBzZXEuUE9TSVh0KGZyb20gPSBjb3VudHNfYnlfaG91clssbWluKGRveV9wb3NpeCldLCB0byA9IGNvdW50c19ieV9ob3VyWyxtYXgoZG95X3Bvc2l4KV0sIGJ5ID0gIkRTVGRheSIpCmNvdW50c19ieV9ob3VyCm1ldGhvZCA8LSBjb3VudHNfYnlfaG91clssdW5pcXVlKG1ldGhvZCldCmhvdXIgPC0gMDoyMwpkYXRlc19hbmRfaG91cnMgPC0gQ0ooZG95X3Bvc2l4LCBob3VyLCBtZXRob2QpCmNvdW50c19ieV9ob3VyIDwtIG1lcmdlKGNvdW50c19ieV9ob3VyLCBkYXRlc19hbmRfaG91cnMsIGJ5ID0gYygiZG95X3Bvc2l4IiwgImhvdXIiLCAibWV0aG9kIiksIGFsbCA9IFRSVUUpCmBgYAoKQWRkIGRheSBvZiB0aGUgd2VlazoKYGBge3J9CmNvdW50c19ieV9ob3VyWywgd2Vla2RheSA6PSB3ZWVrZGF5cyhkb3lfcG9zaXgpXQpgYGAKCkRpc3Rpbmd1aXNoIGJldHdlZW4gc2Nob29sIHllYXJzOgpgYGB7cn0KY291bnRzX2J5X2hvdXJbLCBzY2hvb2xfeWVhciA6PSBpZmVsc2UoZG95X3Bvc2l4IDwgIjIwMTktMDgtMDEiLCAiMTgvMTkiLCAiMTkvMjAiKV0KYGBgCgpBZGQgcXVhcnRlcjoKYGBge3J9CmNvdW50c19ieV9ob3VyWywgcXVhcnRlciA6PSBwYXN0ZTAoeWVhcihkb3lfcG9zaXgpLCAiUSIsIHF1YXJ0ZXIoZG95X3Bvc2l4KSldCmBgYAoKCkFkZCBleGFjdCBzY2hvb2wgY2xvc3VyZSBwZXJpb2QgaW4gYm90aCBzY2hvb2wgeWVhcnM6CmBgYHtyfQpjb3VudHNfYnlfaG91cltzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeCArIDM2NSoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KY291bnRzX2J5X2hvdXJbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gZG95X3Bvc2l4XQoKY291bnRzX2J5X2hvdXJbLCBzY2hvb2xzX2Nsb3NlZCA6PSBkb3lfcG9zaXhfYWxpZ25lZCA+PSBkYXRlX3NjaG9vbHNfY2xvc2VkICYgZG95X3Bvc2l4IDwgZGF0ZV9zY2hvb2xzX29wZW5lZF0KYGBgCgoKQWRkIG1vcmUgc2Vuc2libGUgY291cnNlIG5hbWVzOgpgYGB7cn0KY291bnRzX2J5X2hvdXJbLCBjb3Vyc2UgOj0gaWZlbHNlKG1ldGhvZCA9PSAiR3JhbmRlcyBMaWduZXMiLCAiRnJlbmNoIiwgaWZlbHNlKG1ldGhvZCA9PSAiU3RlcHBpbmcgU3RvbmVzIiwgIkVuZ2xpc2giLCAiR2VybWFuIikpXQpgYGAKCgpTdW0gdHJpYWxzIGJ5IHNjaG9vbCB5ZWFyLCB3ZWVrZGF5IGFuZCBob3VyOgpgYGB7cn0KY291bnRzX2J5X2hvdXJbLCB0cmlhbHNfc2Nob29seWVhciA6PSBzdW0odHJpYWxzLCBuYS5ybSA9IFRSVUUpLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhciwgd2Vla2RheSwgaG91cildCmBgYAoKQWxzbyBzdW0gdHJpYWxzIGJ5IHF1YXJ0ZXIsIHdlZWtkYXkgYW5kIGhvdXI6CmBgYHtyfQpjb3VudHNfYnlfaG91clssIHRyaWFsc19xdWFydGVyIDo9IHN1bSh0cmlhbHMsIG5hLnJtID0gVFJVRSksIGJ5ID0gLihjb3Vyc2UsIHF1YXJ0ZXIsIHdlZWtkYXksIGhvdXIpXQpgYGAKCkFuZCBzdW0gdHJpYWxzIHdpdGhpbiB0aGUgY2xvc3VyZSBwZXJpb2QgYnkgd2Vla2RheSBhbmQgaG91cjoKYGBge3J9CmNvdW50c19ieV9ob3VyW3NjaG9vbHNfY2xvc2VkID09IFRSVUUsIHRyaWFsc19jbG9zZWQgOj0gc3VtKHRyaWFscywgbmEucm0gPSBUUlVFKSwgYnkgPSAuKGNvdXJzZSwgc2Nob29sX3llYXIsIHdlZWtkYXksIGhvdXIpXQpgYGAKCgpgYGB7cn0KdHJpYWxzX2J5X3dkYXlfaG91ciA8LSB1bmlxdWUoY291bnRzX2J5X2hvdXIsIGJ5ID0gYygiY291cnNlIiwgInNjaG9vbF95ZWFyIiwgInF1YXJ0ZXIiLCAic2Nob29sc19jbG9zZWQiLCAid2Vla2RheSIsICJob3VyIikpCgp0cmlhbHNfYnlfd2RheV9ob3VyWywgdHJpYWxzX25vcm1hbGlzZWRfc2Nob29seWVhciA6PSB0cmlhbHNfc2Nob29seWVhciAvIHN1bSh0cmlhbHNfc2Nob29seWVhciksIGJ5ID0gLihjb3Vyc2UpXQp0cmlhbHNfYnlfd2RheV9ob3VyWywgdHJpYWxzX25vcm1hbGlzZWRfcXVhcnRlciA6PSB0cmlhbHNfcXVhcnRlciAvIHN1bSh0cmlhbHNfcXVhcnRlciksIGJ5ID0gLihjb3Vyc2UpXQoKdHJpYWxzX2J5X3dkYXlfaG91clssIHdlZWtkYXkgOj0gb3JkZXJlZCh3ZWVrZGF5LCBsZXZlbHMgPSBjKCJNb25kYXkiLCAiVHVlc2RheSIsICJXZWRuZXNkYXkiLCAiVGh1cnNkYXkiLCAiRnJpZGF5IiwgIlNhdHVyZGF5IiwgIlN1bmRheSIpKV0KIyB0cmlhbHNfYnlfd2RheV9ob3VyWywgd2Vla2RheSA6PSBvcmRlcmVkKHdlZWtkYXksIGxldmVscyA9IGMoIm1hYW5kYWciLCAiZGluc2RhZyIsICJ3b2Vuc2RhZyIsICJkb25kZXJkYWciLCAidnJpamRhZyIsICJ6YXRlcmRhZyIsICJ6b25kYWciKSldCmBgYAoKClBsb3QgaGVhdG1hcCBmb3IgdGhlIHdob2xlIHNjaG9vbCB5ZWFyOgpgYGB7cn0KZ2dwbG90KHRyaWFsc19ieV93ZGF5X2hvdXJbY291cnNlICVpbiUgYygiRW5nbGlzaCIsICJGcmVuY2giKV0sCiAgICAgICBhZXMoeCA9IGhvdXIsIHkgPSByZW9yZGVyKHdlZWtkYXksIGRwbHlyOjpkZXNjKHdlZWtkYXkpKSwgZmlsbCA9IHRyaWFsc19ub3JtYWxpc2VkX3NjaG9vbHllYXIpKSArIAogIGZhY2V0X2dyaWQoc2Nob29sX3llYXIgfiBjb3Vyc2UpICsKICBnZW9tX3RpbGUoY29sb3VyID0gIndoaXRlIiwgc2l6ZSA9IDAuMjUpICsKICBsYWJzKHggPSAiVGltZSBvZiBkYXkgKGhvdXIpIiwKICAgICAgIHkgPSBOVUxMKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgYnJlYWtzID0gc2VxKDAsIDI0LCAzKSkgKwogIHNjYWxlX3lfZGlzY3JldGUoZXhwYW5kID0gYygwLDApKSArIAogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJBIiwgZGlyZWN0aW9uID0gLTEpICsKICBjb29yZF9maXhlZCgpICsKICBndWlkZXMoZmlsbCA9IEZBTFNFKSArCiAgdGhlbWVfcGFwZXIKYGBgCgpQbG90IGhlYXRtYXAgcGVyIHF1YXJ0ZXI6CmBgYHtyLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDE2fQpnZ3Bsb3QodHJpYWxzX2J5X3dkYXlfaG91ciwgYWVzKHggPSBob3VyLCB5ID0gcmVvcmRlcih3ZWVrZGF5LCBkcGx5cjo6ZGVzYyh3ZWVrZGF5KSksIGZpbGwgPSB0cmlhbHNfbm9ybWFsaXNlZF9xdWFydGVyKSkgKyAKICBmYWNldF9ncmlkKHF1YXJ0ZXIgfiBtZXRob2QpICsKICBnZW9tX3RpbGUoY29sb3VyID0gIndoaXRlIiwgc2l6ZSA9IDAuMjUpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9IE5VTEwsCiAgICAgICB0aXRsZSA9ICJBY3Rpdml0ZWl0IHBlciB1dXIgZ2VkdXJlbmRlIGRlIHdlZWsiLAogICAgICAgY2FwdGlvbiA9ICJBYW50YWwgdHJpYWxzIHBlciB3ZWVrZGFnIGVuIHV1ciBpbiBlbGsga3dhcnRhYWwsIGdlbm9ybWFsaXNlZXJkIHBlciBtZXRob2RlLiIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwgMjQsIDMpKSArCiAgc2NhbGVfeV9kaXNjcmV0ZShleHBhbmQgPSBjKDAsMCkpICsgCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gIkEiLCBkaXJlY3Rpb24gPSAtMSkgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGd1aWRlcyhmaWxsID0gRkFMU0UpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikKYGBgCgpQbG90IGhlYXRtYXAgZm9yIHRoZSBwZXJpb2QgaW4gd2hpY2ggc2Nob29scyB3ZXJlIGNsb3NlZDoKYGBge3J9CnRyaWFsc19jbG9zZWQgPC0gdW5pcXVlKHRyaWFsc19ieV93ZGF5X2hvdXJbc2Nob29sc19jbG9zZWQgPT0gVFJVRSwgLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCB3ZWVrZGF5LCBob3VyLCB0cmlhbHNfY2xvc2VkKV0pCgp0cmlhbHNfY2xvc2VkWywgdHJpYWxzX25vcm1hbGlzZWRfY2xvc2VkIDo9IHRyaWFsc19jbG9zZWQgLyBzdW0odHJpYWxzX2Nsb3NlZCksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyKV0KYGBgCgpgYGB7cn0KdHJpYWxzX2Nsb3NlZF9kaWZmIDwtIHRyaWFsc19jbG9zZWRbLCAuKHNjaG9vbF95ZWFyID0gIkNoYW5nZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmlhbHNfY2xvc2VkID0gdHJpYWxzX2Nsb3NlZFtzY2hvb2xfeWVhciA9PSAiMTkvMjAiXSAtIHRyaWFsc19jbG9zZWRbc2Nob29sX3llYXIgPT0gIjE4LzE5Il0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmlhbHNfbm9ybWFsaXNlZF9jbG9zZWQgPSB0cmlhbHNfbm9ybWFsaXNlZF9jbG9zZWRbc2Nob29sX3llYXIgPT0gIjE5LzIwIl0gLSB0cmlhbHNfbm9ybWFsaXNlZF9jbG9zZWRbc2Nob29sX3llYXIgPT0gIjE4LzE5Il0pLCBieSA9IC4oY291cnNlLCB3ZWVrZGF5LCBob3VyKV0KYGBgCgoKYGBge3J9CnBfaGVhdG1hcCA8LSBnZ3Bsb3QodHJpYWxzX2Nsb3NlZFtjb3Vyc2UgJWluJSBjKCJFbmdsaXNoIiwgIkZyZW5jaCIpLF0sCiAgICAgICBhZXMoeCA9IGhvdXIsIHkgPSByZW9yZGVyKHdlZWtkYXksIGRwbHlyOjpkZXNjKHdlZWtkYXkpKSwgZmlsbCA9IHRyaWFsc19ub3JtYWxpc2VkX2Nsb3NlZCkpICsgCiAgZmFjZXRfZ3JpZChzY2hvb2xfeWVhciB+IGNvdXJzZSkgKwogIGdlb21fdGlsZShjb2xvdXIgPSAid2hpdGUiLCBzaXplID0gMC4yNSkgKwogIGxhYnMoeCA9ICJUaW1lIG9mIGRheSAoaG91cikiLAogICAgICAgeSA9IE5VTEwsCiAgICAgICBmaWxsID0gTlVMTCkgKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLCAyNCwgMykpICsKICBzY2FsZV95X2Rpc2NyZXRlKGV4cGFuZCA9IGMoMCwwKSkgKyAKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiQSIsIGRpcmVjdGlvbiA9IC0xKSArCiAgY29vcmRfZml4ZWQoKSArCiAgdGhlbWVfcGFwZXIKCgpwX2hlYXRtYXAKYGBgCgoKTWFrZSBhIHBsb3Qgb2YgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvIHNjaG9vbCB5ZWFycyBkdXJpbmcgdGhlIHNjaG9vbCBjbG9zdXJlIHBlcmlvZDoKYGBge3J9CnBfaGVhdG1hcF9kaWZmIDwtIGdncGxvdCh0cmlhbHNfY2xvc2VkX2RpZmZbY291cnNlICVpbiUgYygiRW5nbGlzaCIsICJGcmVuY2giKSxdLAogICAgICAgYWVzKHggPSBob3VyLCB5ID0gcmVvcmRlcih3ZWVrZGF5LCBkcGx5cjo6ZGVzYyh3ZWVrZGF5KSksIGZpbGwgPSB0cmlhbHNfbm9ybWFsaXNlZF9jbG9zZWQpKSArIAogIGZhY2V0X2dyaWQoc2Nob29sX3llYXIgfiBjb3Vyc2UpICsKICBnZW9tX3RpbGUoY29sb3VyID0gIndoaXRlIiwgc2l6ZSA9IDAuMjUpICsKICBsYWJzKHggPSAiVGltZSBvZiBkYXkgKGhvdXIpIiwKICAgICAgIHkgPSBOVUxMLAogICAgICAgZmlsbCA9IE5VTEwpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwgMjQsIDMpKSArCiAgc2NhbGVfeV9kaXNjcmV0ZShleHBhbmQgPSBjKDAsMCkpICsgCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIodHlwZSA9ICJkaXYiLCBwYWxldHRlID0gIlJkQnUiLCBkaXJlY3Rpb24gPSAtMSwgbGltaXRzID0gYygtMSwgMSkgKiBtYXgoYWJzKHRyaWFsc19jbG9zZWRfZGlmZltjb3Vyc2UgJWluJSBjKCJFbmdsaXNoIiwgIkZyZW5jaCIpLF0kdHJpYWxzX25vcm1hbGlzZWRfY2xvc2VkKSkpICsKICBjb29yZF9maXhlZCgpICsKICB0aGVtZV9wYXBlcgoKcF9oZWF0bWFwX2RpZmYKYGBgCgoKTWFrZSBhIGNvbWJpbmVkIHBsb3QgZm9yIGluIHRoZSBwYXBlcjoKYGBge3J9CnBfaGVhdG1hcF9sZWdlbmQgPC0gZ2V0X2xlZ2VuZChwX2hlYXRtYXApCnBfaGVhdG1hcF9kaWZmX2xlZ2VuZCA8LSBnZXRfbGVnZW5kKHBfaGVhdG1hcF9kaWZmKQoKcF9oZWF0bWFwIDwtIHBfaGVhdG1hcCArIGd1aWRlcyhmaWxsID0gRkFMU0UpCnBfaGVhdG1hcF9kaWZmIDwtIHBfaGVhdG1hcF9kaWZmICsgZ3VpZGVzKGZpbGwgPSBGQUxTRSkKYGBgCgpgYGB7cn0KcGxvdF9ncmlkKAogIHBsb3RfZ3JpZChwX2hlYXRtYXAsIHBfaGVhdG1hcF9kaWZmLAogICAgICAgICAgbmNvbCA9IDEsCiAgICAgICAgICBsYWJlbHMgPSBjKCJBIiwgIkIiKSwKICAgICAgICAgIHJlbF9oZWlnaHRzID0gYygxLCAuNjU1KQogICAgICAgICAgKSwKICBwbG90X2dyaWQocF9oZWF0bWFwX2xlZ2VuZCwgcF9oZWF0bWFwX2RpZmZfbGVnZW5kLAogICAgICAgICAgICBuY29sID0gMSwKICAgICAgICAgICAgYWxpZ24gPSAidmgiLCBheGlzID0gImxydGIiKSwKICBuY29sID0gMiwKICByZWxfd2lkdGhzID0gYygxLCAuMTUpKQoKZ2dzYXZlKCIuLi9vdXRwdXQvY29tYmlfaGVhdG1hcC5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmdnc2F2ZSgiLi4vb3V0cHV0L2NvbWJpX2hlYXRtYXAucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpgYGAKCgojIyBBY3Rpdml0eSBzdHJhdGlmaWVkIGJ5IHllYXIgYW5kIGxldmVsCgpgYGB7cn0KZGIgPC0gZGJfY29ubmVjdCgpCmNvdW50c19zdHJhdCA8LSBkYkdldFF1ZXJ5KGRiLCJTRUxFQ1QgcmVzcG9uc2VzLm1ldGhvZCBBUyAnbWV0aG9kJywKICAgICAgICAgICAgICAgICAgICAgICAgICByZXNwb25zZXMuYm9va19pbmZvX2lkIGFzICdib29rX2luZm9faWQnLAogICAgICAgICAgICAgICAgICAgICAgICAgIERBVEUocmVzcG9uc2VzLmRhdGUgKyAzNjAwLCAndW5peGVwb2NoJykgQVMgJ2RveScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzcG9uc2VzLnVzZXJfaWQgQVMgJ3VzZXInLAogICAgICAgICAgICAgICAgICAgICAgICAgIENPVU5UKCopIEFTICd0cmlhbHMnCiAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSAncmVzcG9uc2VzJwogICAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIHJlc3BvbnNlcy5tZXRob2QsIHJlc3BvbnNlcy5ib29rX2luZm9faWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgREFURShyZXNwb25zZXMuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICByZXNwb25zZXMudXNlcl9pZAogICAgICAgICAgICAgICAgICAgICAgICAiKQpkYl9kaXNjb25uZWN0KGRiKQoKc2V0RFQoY291bnRzX3N0cmF0KQpgYGAKCmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKYm9va19pbmZvIDwtIGRiR2V0UXVlcnkoZGIsICJTRUxFQ1QgKiBGUk9NICdib29rX2luZm8nIikKZGJfZGlzY29ubmVjdChkYikKCnNldERUKGJvb2tfaW5mbykKYGBgCgpBZGQgYm9vayBpbmZvcm1hdGlvbjoKYGBge3J9CmNvdW50c19zdHJhdFtib29rX2luZm8sIG9uID0gImJvb2tfaW5mb19pZCIsIGMoImJvb2tfdGl0bGUiLCAibWV0aG9kX2dyb3VwIikgOj0gLihpLmJvb2tfdGl0bGUsIGkubWV0aG9kX2dyb3VwKV0KYGBgCgpBZGQgYSBzY2hvb2wgeWVhciBjb2x1bW4gKGN1dG9mZiBkYXRlOiAxIEF1Z3VzdCk6CmBgYHtyfQpjb3VudHNfc3RyYXRbLCBkb3lfcG9zaXggOj0gYXMuUE9TSVhjdChkb3kpXQpjb3VudHNfc3RyYXRbLCBzY2hvb2xfeWVhciA6PSBpZmVsc2UoZG95X3Bvc2l4IDwgIjIwMTktMDgtMDEiLCAiMTgvMTkiLCAiMTkvMjAiKV0KYGBgCgpBZGQgc2Vuc2libGUgY291cnNlIG5hbWVzOgpgYGB7cn0KY291bnRzX3N0cmF0WywgY291cnNlIDo9IGlmZWxzZShtZXRob2QgPT0gIkdyYW5kZXMgTGlnbmVzIiwgIkZyZW5jaCIsIGlmZWxzZShtZXRob2QgPT0gIlN0ZXBwaW5nIFN0b25lcyIsICJFbmdsaXNoIiwgIkdlcm1hbiIpKV0KYGBgCgpDb3VudCB0cmlhbHMgYnkgZGF5OgpgYGB7cn0KY291bnRzX3N0cmF0X2J5X2RheSA8LSBjb3VudHNfc3RyYXRbLCAuKHRyaWFsc190b3RhbCA9IHN1bSh0cmlhbHMsIG5hLnJtID0gVFJVRSkpLCBieSA9IC4oc2Nob29sX3llYXIsIGNvdXJzZSwgbWV0aG9kX2dyb3VwLCBib29rX3RpdGxlLCBkb3lfcG9zaXgpXQpzZXRvcmRlcihjb3VudHNfc3RyYXRfYnlfZGF5LCBzY2hvb2xfeWVhciwgY291cnNlLCBtZXRob2RfZ3JvdXAsIGJvb2tfdGl0bGUsIGRveV9wb3NpeCkKYGBgCgpTaW1wbGlmeSBsZXZlbCBuYW1lczoKYGBge3J9CiMgS2VlcCBhbGwgZGlzdGluY3Rpb25zCmNvdW50c19zdHJhdF9ieV9kYXlbLCBib29rX3RpdGxlX3NpbXBsZSA6PSBzdHJpbmdyOjpzdHJfc3ViKGJvb2tfdGl0bGUsIDMsIC0xMCldCmNvdW50c19zdHJhdF9ieV9kYXlbLCBib29rX3RpdGxlX3NpbXBsZSA6PSBmYWN0b3IoYm9va190aXRsZV9zaW1wbGUsIGxldmVscyA9IGMoInZtYm8gYi9sd29vIiwgInZtYm8gYiIsICJ2bWJvIGJrIiwgInZtYm8gayIsICJ2bWJvIGtndCIsICJ2bWJvLWd0IiwgInZtYm8gZ3QiLCAidm1iby1ndC9oYXZvIiwgInZtYm8gKHQpaHYiLCAiaGF2byIsICJoYXZvIHZ3byIsICJ2d28iKSldCgojIFNpbXBsaWZ5IHRvIHRocmVlIGxldmVscwpjb3VudHNfc3RyYXRfYnlfZGF5WywgbGV2ZWwgOj0gZHBseXI6OmNhc2Vfd2hlbigKICBncmVwbCgidm1ibyIsIGJvb2tfdGl0bGUpIH4gIlByZS12b2NhdGlvbmFsXG4odm1ibykiLAogIGdyZXBsKCJoYXZvIiwgYm9va190aXRsZSkgfiAiR2VuZXJhbCBzZWNvbmRhcnlcbihoYXZvKSIsCiAgZ3JlcGwoInZ3byIsIGJvb2tfdGl0bGUpIH4gIlByZS11bml2ZXJzaXR5XG4odndvKSIsCiAgVFJVRSB+ICJPdGhlciIpXQpjb3VudHNfc3RyYXRfYnlfZGF5WywgbGV2ZWwgOj0gZmFjdG9yKGxldmVsLCBsZXZlbHMgPSBjKCJPdGhlciIsICJQcmUtdm9jYXRpb25hbFxuKHZtYm8pIiwgIkdlbmVyYWwgc2Vjb25kYXJ5XG4oaGF2bykiLCAiUHJlLXVuaXZlcnNpdHlcbih2d28pIikpXQpgYGAKClNpbXBsaWZ5IHllYXIgbmFtZXM6CmBgYHtyfQpjb3VudHNfc3RyYXRfYnlfZGF5WywgeWVhciA6PSBkcGx5cjo6Y2FzZV93aGVuKAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgMSAoNWUgRWQuKSIgfiAiWWVhciAxIiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDIgKDVlIEVkLikiIH4gIlllYXIgMiIsCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAzICg1ZSBFZC4pIiB+ICJZZWFyIDMiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgMy80ICg1ZSBFZC4pIiB+ICJZZWFyIDMvNCIsCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciA0ICg1ZSBFZC4pIiB+ICJZZWFyIDQiLAogIG1ldGhvZF9ncm91cCA9PSAiVHdlZWRlIEZhc2UgKDZlIEVkLikiIH4gIlR3ZWVkZSBGYXNlIiwKICBUUlVFIH4gIk90aGVyIildCmBgYAoKCkFsaWduIHNjaG9vbCB5ZWFyczoKYGBge3J9CmNvdW50c19zdHJhdF9ieV9kYXlbc2Nob29sX3llYXIgPT0gIjE4LzE5IiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gYXMuUE9TSVhjdChkb3lfcG9zaXggKyAzNjUqMjQqNjAqNjAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIildCmNvdW50c19zdHJhdF9ieV9kYXlbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gZG95X3Bvc2l4XQpgYGAKClVzZSBjdXQuRGF0ZSgpIHRvIGJpbiBkYXRlcyBieSB3ZWVrLiBFYWNoIGRheSBpcyBhc3NpZ25lZCB0aGUgZGF0ZSBvZiB0aGUgbW9zdCByZWNlbnQgTW9uZGF5LgpgYGB7cn0KY291bnRzX3N0cmF0X2J5X2RheVssIGRveV9wb3NpeF9hbGlnbmVkX3dlZWsgOj0gY3V0LlBPU0lYdChkb3lfcG9zaXhfYWxpZ25lZCwgIndlZWsiKV0KY291bnRzX3N0cmF0X2J5X2RheVssIHRyaWFsc190b3RhbF93ZWVrIDo9IHN1bSh0cmlhbHNfdG90YWwsIG5hLnJtID0gVFJVRSksIGJ5ID0gLihzY2hvb2xfeWVhciwgY291cnNlLCBtZXRob2RfZ3JvdXAsIGJvb2tfdGl0bGVfc2ltcGxlLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrKV0KCmNvdW50c19zdHJhdF9ieV9kYXlbLCB0cmlhbHNfdG90YWxfd2Vla19sZXZlbCA6PSBzdW0odHJpYWxzX3RvdGFsKSwgYnkgPSAuKHNjaG9vbF95ZWFyLCBjb3Vyc2UsIG1ldGhvZF9ncm91cCwgbGV2ZWwsIGRveV9wb3NpeF9hbGlnbmVkX3dlZWspXQpgYGAKCgpTdW1tYXJpc2UgaW5jcmVhc2UgZHVyaW5nIGxvY2tkb3duOgpgYGB7cn0KY291bnRzX3N0cmF0X2luY3JlYXNlIDwtIGNvdW50c19zdHJhdF9ieV9kYXlbYmV0d2Vlbihkb3lfcG9zaXhfYWxpZ25lZCwgZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgZGF0ZV9zY2hvb2xzX29wZW5lZCksIC4odHJpYWxzX2xvY2tkb3duID0gc3VtKHRyaWFsc190b3RhbCkpLCBieSA9IC4oY291cnNlLCBib29rX3RpdGxlX3NpbXBsZSwgbWV0aG9kX2dyb3VwLCB5ZWFyLCBzY2hvb2xfeWVhcildCmNvdW50c19zdHJhdF9pbmNyZWFzZVssIGluY3JlYXNlIDo9IHRyaWFsc19sb2NrZG93blsyXS90cmlhbHNfbG9ja2Rvd25bMV0sIGJ5ID0gLihjb3Vyc2UsIGJvb2tfdGl0bGVfc2ltcGxlLCBtZXRob2RfZ3JvdXAsIHllYXIpXQpjb3VudHNfc3RyYXRfaW5jcmVhc2VbLCBpbmNyZWFzZV9wY3QgOj0gcGFzdGUwKCJDaGFuZ2U6XG4iLCBzY2FsZXM6OnBlcmNlbnQoaW5jcmVhc2UsIGFjY3VyYWN5ID0gMikpXQoKY291bnRzX3N0cmF0X2luY3JlYXNlX2xldmVsIDwtIGNvdW50c19zdHJhdF9ieV9kYXlbYmV0d2Vlbihkb3lfcG9zaXhfYWxpZ25lZCwgZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgZGF0ZV9zY2hvb2xzX29wZW5lZCksIC4odHJpYWxzX2xvY2tkb3duID0gc3VtKHRyaWFsc190b3RhbCkpLCBieSA9IC4oY291cnNlLCBsZXZlbCwgbWV0aG9kX2dyb3VwLCB5ZWFyLCBzY2hvb2xfeWVhcildCmNvdW50c19zdHJhdF9pbmNyZWFzZV9sZXZlbFssIGluY3JlYXNlIDo9IHRyaWFsc19sb2NrZG93blsyXS90cmlhbHNfbG9ja2Rvd25bMV0sIGJ5ID0gLihjb3Vyc2UsIGxldmVsLCBtZXRob2RfZ3JvdXAsIHllYXIpXQpjb3VudHNfc3RyYXRfaW5jcmVhc2VfbGV2ZWxbLCBpbmNyZWFzZV9wY3QgOj0gcGFzdGUwKCJDaGFuZ2U6XG4iLCBzY2FsZXM6OnBlcmNlbnQoaW5jcmVhc2UsIGFjY3VyYWN5ID0gMikpXQpgYGAKCgojIyMgRnJlbmNoCmBgYHtyfQpnZ3Bsb3QoY291bnRzX3N0cmF0X2J5X2RheVtjb3Vyc2UgPT0gIkZyZW5jaCJdLCAKICAgICAgIGFlcyhncm91cCA9IHNjaG9vbF95ZWFyLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQoYm9va190aXRsZV9zaW1wbGUgfiBtZXRob2RfZ3JvdXApICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTJlNSwgeW1heCA9IDIuMmU2LCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyKSArCiAgZ2VvbV9yaWJib24oYWVzKHggPSBkb3lfcG9zaXhfYWxpZ25lZCwgeW1pbiA9IDAsIHltYXggPSB0cmlhbHNfdG90YWxfd2VlaywgKSwgYWxwaGEgPSAuMikgKwogIGdlb21fdGV4dChkYXRhID0gY291bnRzX3N0cmF0X2luY3JlYXNlW2NvdXJzZSA9PSAiRnJlbmNoIiAmIHNjaG9vbF95ZWFyID09ICIxOS8yMCJdLCAKICAgICAgICAgICAgYWVzKGxhYmVsID0gaW5jcmVhc2VfcGN0KSwKICAgICAgICAgICAgeCA9IGFzLlBPU0lYY3QoKGFzLm51bWVyaWMoZGF0ZV9zY2hvb2xzX2Nsb3NlZCkgKyBhcy5udW1lcmljKGRhdGVfc2Nob29sc19vcGVuZWQpKS8yLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpLAogICAgICAgICAgICB5ID0gMy42ZTUsCiAgICAgICAgICAgIGNvbG91ciA9ICJibGFjayIsCiAgICAgICAgICAgIHZqdXN0ID0gMSwKICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTExLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAxLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAzLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA1LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCAzLjc1ZTUpLCBsYWJlbHMgPSBudW1iZXJfZm9ybWF0KSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlRyaWFscyBwZXIgd2VlayIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIsCiAgICAgICB0aXRsZSA9ICJGcmVuY2giKSArCiAgdGhlbWVfcGFwZXIKCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfZnJlbmNoLnBkZiIsIHdpZHRoID0gMTQsIGhlaWdodCA9IDEwKQpnZ3NhdmUoIi4uL291dHB1dC90cmlhbF9oaXN0X2ZyZW5jaC5wbmciLCB3aWR0aCA9IDE0LCBoZWlnaHQgPSAxMCkKYGBgCgpTdHJlYW1saW5lZCB2ZXJzaW9uIGZvciBpbiB0aGUgcGFwZXI6CmBgYHtyfQpnZ3Bsb3QoY291bnRzX3N0cmF0X2J5X2RheVtjb3Vyc2UgPT0gIkZyZW5jaCJdLCAKICAgICAgIGFlcyhncm91cCA9IHNjaG9vbF95ZWFyLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQobGV2ZWwgfiB5ZWFyKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IC0yZTUsIHltYXggPSAyLjJlNiwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMikgKwogIGdlb21fcmliYm9uKGFlcyh4ID0gZG95X3Bvc2l4X2FsaWduZWQsIHltaW4gPSAwLCB5bWF4ID0gdHJpYWxzX3RvdGFsX3dlZWtfbGV2ZWwsICksIGFscGhhID0gLjIpICsKICBnZW9tX3RleHQoZGF0YSA9IGNvdW50c19zdHJhdF9pbmNyZWFzZV9sZXZlbFtjb3Vyc2UgPT0gIkZyZW5jaCIgJiBzY2hvb2xfeWVhciA9PSAiMTkvMjAiXSwgCiAgICAgICAgICAgIGFlcyhsYWJlbCA9IGluY3JlYXNlX3BjdCksCiAgICAgICAgICAgIHggPSBhcy5QT1NJWGN0KChhcy5udW1lcmljKGRhdGVfc2Nob29sc19jbG9zZWQpICsgYXMubnVtZXJpYyhkYXRlX3NjaG9vbHNfb3BlbmVkKSkvMiwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKSwKICAgICAgICAgICAgeSA9IDMuNmU1LAogICAgICAgICAgICBjb2xvdXIgPSAiYmxhY2siLAogICAgICAgICAgICB2anVzdCA9IDEsCiAgICAgICAgICAgIHNpemUgPSByZWwoMi43NSksCiAgICAgICAgICAgIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgMy43NWU1KSwgbGFiZWxzID0gbnVtYmVyX2Zvcm1hdCkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJUcmlhbHMgcGVyIHdlZWsiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiKSArCiAgdGhlbWVfcGFwZXIKCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfZnJlbmNoX2xldmVsLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSkKZ2dzYXZlKCIuLi9vdXRwdXQvdHJpYWxfaGlzdF9mcmVuY2hfbGV2ZWwucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpgYGAKCiMjIyBFbmdsaXNoCgpgYGB7cn0KZ2dwbG90KGNvdW50c19zdHJhdF9ieV9kYXlbY291cnNlID09ICJFbmdsaXNoIl0sIAogICAgICAgYWVzKGdyb3VwID0gc2Nob29sX3llYXIsIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChib29rX3RpdGxlX3NpbXBsZSB+IG1ldGhvZF9ncm91cCkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAtMmU1LCB5bWF4ID0gMi4yZTYsIGZpbGwgPSAiZ3JleTkyIiwgY29sb3VyID0gImdyZXk1MCIsIGx0eSA9IDIpICsKICBnZW9tX3JpYmJvbihhZXMoeCA9IGRveV9wb3NpeF9hbGlnbmVkLCB5bWluID0gMCwgeW1heCA9IHRyaWFsc190b3RhbF93ZWVrLCApLCBhbHBoYSA9IC4yKSArCiAgZ2VvbV90ZXh0KGRhdGEgPSBjb3VudHNfc3RyYXRfaW5jcmVhc2VbY291cnNlID09ICJFbmdsaXNoIiAmIHNjaG9vbF95ZWFyID09ICIxOS8yMCJdLCAKICAgICAgICAgICAgYWVzKGxhYmVsID0gaW5jcmVhc2VfcGN0KSwKICAgICAgICAgICAgeCA9IGFzLlBPU0lYY3QoKGFzLm51bWVyaWMoZGF0ZV9zY2hvb2xzX2Nsb3NlZCkgKyBhcy5udW1lcmljKGRhdGVfc2Nob29sc19vcGVuZWQpKS8yLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpLAogICAgICAgICAgICB5ID0gMy42ZTUsCiAgICAgICAgICAgIGNvbG91ciA9ICJibGFjayIsCiAgICAgICAgICAgIHZqdXN0ID0gMSwKICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTExLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAxLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAzLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA1LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCAzLjc1ZTUpLCBsYWJlbHMgPSBudW1iZXJfZm9ybWF0KSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlRyaWFscyBwZXIgd2VlayIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIsCiAgICAgICB0aXRsZSA9ICJFbmdsaXNoIikgKwogIHRoZW1lX3BhcGVyCgpnZ3NhdmUoIi4uL291dHB1dC90cmlhbF9oaXN0X2VuZ2xpc2gucGRmIiwgd2lkdGggPSAxNCwgaGVpZ2h0ID0gMTApCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfZW5nbGlzaC5wbmciLCB3aWR0aCA9IDE0LCBoZWlnaHQgPSAxMCkKYGBgCgpTdHJlYW1saW5lZCB2ZXJzaW9uIGZvciBpbiB0aGUgcGFwZXI6CmBgYHtyfQpnZ3Bsb3QoY291bnRzX3N0cmF0X2J5X2RheVtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIl0sIAogICAgICAgYWVzKGdyb3VwID0gc2Nob29sX3llYXIsIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChsZXZlbCB+IHllYXIpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTJlNSwgeW1heCA9IDIuMmU2LCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyKSArCiAgZ2VvbV9yaWJib24oYWVzKHggPSBkb3lfcG9zaXhfYWxpZ25lZCwgeW1pbiA9IDAsIHltYXggPSB0cmlhbHNfdG90YWxfd2Vla19sZXZlbCwgKSwgYWxwaGEgPSAuMikgKwogIGdlb21fdGV4dChkYXRhID0gY291bnRzX3N0cmF0X2luY3JlYXNlX2xldmVsW2NvdXJzZSA9PSAiRW5nbGlzaCIgJiBsZXZlbCAhPSAiT3RoZXIiICYgc2Nob29sX3llYXIgPT0gIjE5LzIwIl0sIAogICAgICAgICAgICBhZXMobGFiZWwgPSBpbmNyZWFzZV9wY3QpLAogICAgICAgICAgICB4ID0gYXMuUE9TSVhjdCgoYXMubnVtZXJpYyhkYXRlX3NjaG9vbHNfY2xvc2VkKSArIGFzLm51bWVyaWMoZGF0ZV9zY2hvb2xzX29wZW5lZCkpLzIsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIiksCiAgICAgICAgICAgIHkgPSA5LjZlNSwKICAgICAgICAgICAgY29sb3VyID0gImJsYWNrIiwKICAgICAgICAgICAgdmp1c3QgPSAxLAogICAgICAgICAgICBzaXplID0gcmVsKDIuNzUpLAogICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLCAKICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGFzLlBPU0lYY3QoYygKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTAtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDQtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDYtMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgbGltaXRzID0gYXMuUE9TSVhjdChjKCIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGRhdGVfbGFiZWxzID0gIiViIikgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsaW1pdHMgPSBjKDAsIDFlNiksIGxhYmVscyA9IG51bWJlcl9mb3JtYXQpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiVHJpYWxzIHBlciB3ZWVrIiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBmaWxsID0gIlNjaG9vbCB5ZWFyIikgKwogIHRoZW1lX3BhcGVyCgpnZ3NhdmUoIi4uL291dHB1dC90cmlhbF9oaXN0X2VuZ2xpc2hfbGV2ZWwucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpnZ3NhdmUoIi4uL291dHB1dC90cmlhbF9oaXN0X2VuZ2xpc2hfbGV2ZWwucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpgYGAKCgoKIyMgUXVlc3Rpb24gdHlwZQoKVGhlcmUgYXJlIGRpZmZlcmVudCBxdWVzdGlvbiBmb3JtYXRzOiBvcGVuLWFuc3dlciwgaW4gd2hpY2ggdGhlIHN0dWRlbnQgdHlwZXMgdGhlIGFuc3dlciwgYW5kIG11bHRpcGxlLWNob2ljZSwgaW4gd2hpY2ggdGhlIHN0dWRlbnQgc2VsZWN0cyB0aGUgYW5zd2VyIGZyb20gYSBzZXQgb2YgMyBvciA0IG9wdGlvbnMuCgpgYGB7cn0KZGIgPC0gZGJfY29ubmVjdCgpCnF1ZXN0aW9uX3R5cGUgPC0gZGJHZXRRdWVyeShkYiwgCiAgICAgICAgICAgICAgICAgICAgICAiU0VMRUNUIHIubWV0aG9kIEFTICdtZXRob2QnLAogICAgICAgICAgICAgICAgICAgICAgREFURShyLmRhdGUgKyAzNjAwLCAndW5peGVwb2NoJykgQVMgJ2RveScsCiAgICAgICAgICAgICAgICAgICAgICByLmNob2ljZXMgQVMgJ2Nob2ljZXMnLAogICAgICAgICAgICAgICAgICAgICAgQ09VTlQoKikgQVMgJ24nCiAgICAgICAgICAgICAgICAgICAgICBGUk9NICdyZXNwb25zZXMnIHIKICAgICAgICAgICAgICAgICAgICAgIFdIRVJFIHIuc3R1ZHkgPT0gMAogICAgICAgICAgICAgICAgICAgICAgR1JPVVAgQlkgci5tZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSwKICAgICAgICAgICAgICAgICAgICAgIHIuY2hvaWNlcyIKKQpzZXREVChxdWVzdGlvbl90eXBlKQpkYl9kaXNjb25uZWN0KGRiKQpgYGAKCkFkZCBhIHNjaG9vbCB5ZWFyIGNvbHVtbiAoY3V0b2ZmIGRhdGU6IDEgQXVndXN0KToKYGBge3J9CnF1ZXN0aW9uX3R5cGVbLCBkb3lfcG9zaXggOj0gYXMuUE9TSVhjdChkb3kpXQpxdWVzdGlvbl90eXBlWywgc2Nob29sX3llYXIgOj0gaWZlbHNlKGRveV9wb3NpeCA8ICIyMDE5LTA4LTAxIiwgIjE4LzE5IiwgIjE5LzIwIildCmBgYAoKQWRkIHNlbnNpYmxlIGNvdXJzZSBuYW1lczoKYGBge3J9CnF1ZXN0aW9uX3R5cGVbLCBjb3Vyc2UgOj0gaWZlbHNlKG1ldGhvZCA9PSAiR3JhbmRlcyBMaWduZXMiLCAiRnJlbmNoIiwgaWZlbHNlKG1ldGhvZCA9PSAiU3RlcHBpbmcgU3RvbmVzIiwgIkVuZ2xpc2giLCAiR2VybWFuIikpXQpgYGAKCkFsaWduIHNjaG9vbCB5ZWFyczoKYGBge3J9CnF1ZXN0aW9uX3R5cGVbc2Nob29sX3llYXIgPT0gIjE4LzE5IiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gYXMuUE9TSVhjdChkb3lfcG9zaXggKyAzNjUqMjQqNjAqNjAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIildCnF1ZXN0aW9uX3R5cGVbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gZG95X3Bvc2l4XQpgYGAKClVzZSBjdXQuRGF0ZSgpIHRvIGJpbiBkYXRlcyBieSB3ZWVrLiBFYWNoIGRheSBpcyBhc3NpZ25lZCB0aGUgZGF0ZSBvZiB0aGUgbW9zdCByZWNlbnQgTW9uZGF5LgpgYGB7cn0KcXVlc3Rpb25fdHlwZVssIGRveV9wb3NpeF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4LCAid2VlayIpXQpxdWVzdGlvbl90eXBlWywgZG95X3Bvc2l4X2FsaWduZWRfd2VlayA6PSBjdXQuUE9TSVh0KGRveV9wb3NpeF9hbGlnbmVkLCAid2VlayIpXQpgYGAKCmBgYHtyfQpxdWVzdGlvbl90eXBlX2J5X3dlZWsgPC0gcXVlc3Rpb25fdHlwZVssIC4obiA9IHN1bShuKSksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCBjaG9pY2VzKV0KYGBgCgpgYGB7cn0KZ2dwbG90KHF1ZXN0aW9uX3R5cGVfYnlfd2Vla1tjb3Vyc2UgJWluJSBjKCJFbmdsaXNoIiwgIkZyZW5jaCIpXSwgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gbiwgZ3JvdXAgPSBpbnRlcmFjdGlvbihzY2hvb2xfeWVhcixhcy5mYWN0b3IoY2hvaWNlcykpLCBjb2xvdXIgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKGNvdXJzZSB+IGNob2ljZXMpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiVHJpYWxzIiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIpICsKICB0aGVtZV9wYXBlcgoKYGBgCgpgYGB7cn0KcXVlc3Rpb25fdHlwZVssIC4obiA9IHN1bShuKSksIGJ5ID0gLihjb3Vyc2UsIG1jcSA9IGNob2ljZXM+MSwgc2Nob29sX3llYXIpXVssIC4ocGVyY19tY3EgPSBuW21jcSA9PSBUUlVFXS9zdW0obikpLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhcildCmBgYAoKVGhlcmUgaXMgYSBjbGVhciBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGxhbmd1YWdlcyBpbiB0aGUgcXVlc3Rpb24gZm9ybWF0IHVzZWQ6IEVuZ2xpc2ggdXNlcyBhbG1vc3QgZXhjbHVzaXZlbHkgNC1hbHRlcm5hdGl2ZSBNQ1FzLCB3aGlsZSBGcmVuY2ggdXNlcyBhIG1peCBvZiBNQ1FzIChpbmNsdWRpbmcgYSBzbWFsbCBudW1iZXIgb2YgMy1hbHRlcm5hdGl2ZSBxdWVzdGlvbnMpIGFuZCBvcGVuLWFuc3dlciBxdWVzdGlvbnMuCgoKIyBTZXNzaW9uIGluZm8KYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCgo=